【Luogu 4630】[APIO2018] Duathlon 铁人两项(圆方树)

目录

  • 题目
      • 题目描述
      • 输入格式
      • 输出格式
      • 输入输出样例
      • 说明/提示
  • 思路
      • 圆方树
          • 定义
          • 性质
          • 构建
  • 代码

题目

题目描述

比特镇的路网由 m m m 条双向道路连接的 n n n 个交叉路口组成。

最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。

比赛的路线要按照如下方法规划:

先选择三个两两互不相同的路口 s , c s, c s,c f f f,分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
选择一条从 s s s 出发,经过 c c c 最终到达 f f f 的路径。考虑到安全因素,选择的路径经过同一个点至多一次。
在规划路径之前,镇长想请你帮忙计算,总共有多少种不同的选取 s , c s, c s,c f f f 的方案,使得在第 2 2 2 步中至少能设计出一条满足要求的路径。

输入格式

第一行包含两个整数 n n n m m m ,分别表示交叉路口和双向道路的数量。

接下来 m m m 行,每行两个整数 v i , u i v_i, u_i vi,ui。表示存在一条双向道路连接交叉路口 v i , u i   ( 1 ≤ v i , u i ≤ n , v i ≠ u i ) v_i, u_i\:(1 ≤ v_i, u_i ≤ n,v_i \neq u_i) vi,ui(1vi,uin,vi=ui)

保证任意两个交叉路口之间,至多被一条双向道路直接连接。

输出格式

输出一行,包括一个整数,表示能满足要求的不同的选取 s , c s, c s,c f f f 的方案数。

输入输出样例

输入 #1
4 3
1 2
2 3
3 4
输出 #1
8

输入 #2
4 4
1 2
2 3
3 4
4 2
输出 #2
14

说明/提示

提示

在第一个样例中,有以下 8 8 8 种不同的选择 ( s , c , f ) (s,c,f) (s,c,f) 的方案: ( 1 , 2 , 3 ) , ( 1 , 2 , 4 ) , ( 1 , 3 , 4 ) , ( 2 , 3 , 4 ) , ( 3 , 2 , 1 ) , ( 4 , 2 , 1 ) , ( 4 , 3 , 1 ) , ( 4 , 3 , 2 ) (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4),(3, 2, 1), (4, 2, 1), (4, 3, 1), (4, 3, 2) (1,2,3),(1,2,4),(1,3,4),(2,3,4),(3,2,1),(4,2,1),(4,3,1),(4,3,2)

在第二个样例中,有以下 14 14 14 种不同的选择 ( s , c , f ) (s, c, f) (s,c,f) 的方案: ( 1 , 2 , 3 ) , ( 1 , 2 , 4 ) , ( 1 , 3 , 4 ) , ( 1 , 4 , 3 ) , ( 2 , 3 , 4 ) , ( 2 , 4 , 3 ) , ( 3 , 2 , 1 ) , ( 3 , 2 , 4 ) , ( 3 , 4 , 1 ) , (1, 2, 3), (1, 2, 4), (1, 3, 4), (1, 4, 3), (2, 3, 4), (2, 4,3),(3,2,1),(3,2,4),(3,4,1), (1,2,3),(1,2,4),(1,3,4),(1,4,3),(2,3,4),(2,4,3),(3,2,1),(3,2,4),(3,4,1), ( 3 , 4 , 2 ) , ( 4 , 2 , 1 ) , ( 4 , 2 , 3 ) , ( 4 , 3 , 1 ) , ( 4 , 3 , 2 ) (3,4,2), (4, 2, 1), (4, 2, 3), (4, 3, 1), (4, 3, 2) (3,4,2),(4,2,1),(4,2,3),(4,3,1),(4,3,2)

子任务(注:这里给出的子任务与本题在这里的最终评测无关,仅供参考)

Subtask 1 (points: 5 5 5): n ≤ 10 , m ≤ 100 n \leq 10, m \leq 100 n10,m100
Subtask 2 (points: 11 11 11): n ≤ 50 , m ≤ 100 n \leq 50, m \leq 100 n50,m100
Subtask 3 (points: 8 8 8): n ≤ 100000 n \leq 100000 n100000,每个交叉路口至多作为两条双向道路的端点
Subtask 4 (points: 10 10 10): n ≤ 1000 n \leq 1000 n1000,在路网中不存在环(存在环是指存在一个长度为 k ( k ≥ 3 ) k (k ≥ 3) k(k3) 的交叉路口序列 v 1 , v 2 , . . . , v k v_1, v_2,..., v_k v1,v2,...,vk,序列中的路口编号两两不同,且对于 i i i 1 1 1 k − 1 k−1 k1,有一条双向道路直接连接路口 v i v_i vi v i + 1 v_{i+1} vi+1,且有一条双向道路直接连接路口 v k v_k vk v 1 v_1 v1
Subtask 5(points: 13 13 13): n ≤ 100000 n \leq 100000 n100000,在路网中不存在环
Subtask 6(points: 15 15 15): n ≤ 1000 n \leq 1000 n1000,对于每个交叉路口,至多被一个环包含
Subtask 7(points: 20 20 20): n ≤ 100000 n \leq 100000 n100000,对于每个交叉路口,至多被一个环包含
Subtask 8(points: 8 8 8): n ≤ 1000 , m ≤ 2000 n \leq 1000, m \leq 2000 n1000,m2000
Subtask 9(points: 10 10 10): n ≤ 100000 , m ≤ 200000 n \leq 100000, m \leq 200000 n100000,m200000

题目传送门

思路

对于无向图上的简单路径问题,一般我们会试用 t a r j a n tarjan tarjan 这个优(gui)秀(chu)的东西。
对于这一题,我们需要先将每一个点双联通分量缩起来。
于是解引出了我国庆集训刚学习的 毒瘤 东西:
——圆方树
会圆方树的奆佬们请自觉忽略以下文字。

圆方树

先放张图
【Luogu 4630】[APIO2018] Duathlon 铁人两项(圆方树)_第1张图片
………………………………………


定义

在圆方树中,原来的每个点对应一个圆点,每一个点双对应一个方点。
所以共有 n + c n+c n+c 个点,其中 n n n 是原图点数, c c c 是原图点双连通分量的个数。
造一颗圆方树,就可以支持一些对无向图路径的操作了。
聪明机智 的我又开始 盗图 了。
【Luogu 4630】[APIO2018] Duathlon 铁人两项(圆方树)_第2张图片
(不要face的把图片下面的网址改成了红色。。。)

性质

有一些 显而易见 的性质:

  • 对于任意无向图 G = { V , E } G=\{V , E\} G={V,E} 均满足建出来的是森林。
  • 对于任意连通无向图 G G G 均满足建出来的是一颗无根树。
  • 方点只能和圆点相邻,同样,圆点只能和方点相邻。
  • 如果两个方点有公共相邻的圆点,那这个圆点就是这两个方点代表的点双的割点。
  • 原图的割点是圆方树中度数 > 1 >1 >1 的圆点。
  • 方点的点度是点双联通分量的大小。
  • 我不知道(编不下去)了……
构建

似乎很简单?
首先 T a r j a n Tarjan Tarjan 缩点,把每个大小超过 1 1 1 的环里面的所有点都向一个新点(方点)连边。
然后把多出来的连接两个圆点的边直接给连上。
就完事了。。。


那么这题怎么做呢?
这好像就是一道圆方树裸题。
具体见下:

  • 把圆方树建出来,在树中任意枚举两个圆点作为 s s s f f f ,然后考虑 c c c 有多少种选法,两个点路径上每个点双中的点都可以选。
  • 令每个圆点的权值为 − 1 −1 1,每个方点的权值为点双大小,可以得到答案就是两点之间的点权和,也就是说我们要求圆方树上 n 2 n^2 n2 条圆点到圆点的路径的权值和。
  • 可以想到计算每个点被算了多少次,可以用一遍 d f s dfs dfs得到。这样就可以在线性的时间内做完这题了。

一些注意事项

  • a n s ans ans ×   2 \times\:2 ×2
  • a n s ans ans 要开 l o n g   l o n g long\:long longlong

(其实就是我调了很长时间的原因……)

代码

#include 

using namespace std;
int n, m, tot, dfn[100010], low[100010], lev, tp;
struct node {
	int y, nxt;
} e[500010];
int val[200010], siz[200010], st[100010], head[500010];
long long ans;
vector <int> son[200010];

void addedge(int x, int y) {
	e[++tot].y = y;
	e[tot].nxt = head[x];
	head[x] = tot;
}

void tarjan(int x, int pre) {
	dfn[x] = low[x] = ++lev;
	st[++tp] = x;
	siz[x] = 1; val[x] = -1;
	int now = 0, y;
	for (int i = head[x]; i; i = e[i].nxt) {
		y = e[i].y;
		if (!dfn[y]) {
			tarjan(y, x);
			low[x] = min(low[x], low[y]);
			if (low[y] < dfn[x]) continue;
			val[++m] = 1;
			do {
				now = st[tp--];
				son[m].push_back(now);
				siz[m] += siz[now];
				val[m]++;
			} while (now != y);
            son[x].push_back(m);
			siz[x] += siz[m];
		}
		else low[x] = min(low[x], dfn[y]);
	}
	return ;
}

void search(int x, int cnt) {
	int tmp = x <= n;
	for (int i = 0; i < son[x].size(); i++)
		search(son[x][i], cnt), ans += (long long) tmp*siz[son[x][i]]*val[x], tmp += siz[son[x][i]];
	ans += (long long) siz[x] * (cnt-siz[x]) * val[x];
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		int v, u;
		scanf("%d%d", &u, &v);
		addedge(u, v); addedge(v, u);
	}
	m = n;
	for (int i = 1; i <= n; i++)
		if (!dfn[i]) tarjan(i, 0), search(i, siz[i]);
	printf("%lld\n", ans*2);
	return 0;
}

你可能感兴趣的:(图论,#,圆方树)