比特镇的路网由 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(1≤vi,ui≤n,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 n≤10,m≤100
Subtask 2 (points: 11 11 11): n ≤ 50 , m ≤ 100 n \leq 50, m \leq 100 n≤50,m≤100
Subtask 3 (points: 8 8 8): n ≤ 100000 n \leq 100000 n≤100000,每个交叉路口至多作为两条双向道路的端点
Subtask 4 (points: 10 10 10): n ≤ 1000 n \leq 1000 n≤1000,在路网中不存在环(存在环是指存在一个长度为 k ( k ≥ 3 ) k (k ≥ 3) k(k≥3) 的交叉路口序列 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 k−1,有一条双向道路直接连接路口 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 n≤100000,在路网中不存在环
Subtask 6(points: 15 15 15): n ≤ 1000 n \leq 1000 n≤1000,对于每个交叉路口,至多被一个环包含
Subtask 7(points: 20 20 20): n ≤ 100000 n \leq 100000 n≤100000,对于每个交叉路口,至多被一个环包含
Subtask 8(points: 8 8 8): n ≤ 1000 , m ≤ 2000 n \leq 1000, m \leq 2000 n≤1000,m≤2000
Subtask 9(points: 10 10 10): n ≤ 100000 , m ≤ 200000 n \leq 100000, m \leq 200000 n≤100000,m≤200000
题目传送门
对于无向图上的简单路径问题,一般我们会试用 t a r j a n tarjan tarjan 这个优(gui)秀(chu)的东西。
对于这一题,我们需要先将每一个点双联通分量缩起来。
于是解引出了我国庆集训刚学习的 毒瘤 东西:
——圆方树
会圆方树的奆佬们请自觉忽略以下文字。
在圆方树中,原来的每个点对应一个圆点,每一个点双对应一个方点。
所以共有 n + c n+c n+c 个点,其中 n n n 是原图点数, c c c 是原图点双连通分量的个数。
造一颗圆方树,就可以支持一些对无向图路径的操作了。
聪明机智 的我又开始 盗图 了。
(不要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得到。这样就可以在线性的时间内做完这题了。
一些注意事项:
(其实就是我调了很长时间的原因……)
#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;
}