Address
Solution
继 APIO 2018 之后,圆方树重出江湖!
圆方树大概就是,将图的每个点双连通分量建一个方点,把连通分量里的点全部连向这个方点,形成一棵树。原图中的点为圆点。
圆方树能处理与图连通性有关的许多问题。
回到原问题,问题相当于求对于所有的有序点对
(
u
,
v
)
,
u
≠
v
(u,v),u\ne v
(u,v),u̸=v :
∑
(
u
,
v
)
可
能
出
现
在
u
到
v
的
简
单
路
径
上
的
点
数
,
不
包
括
u
和
v
\sum_{(u,v)}可能出现在u到v的简单路径上的点数,不包括u和v
(u,v)∑可能出现在u到v的简单路径上的点数,不包括u和v
分情况讨论可能出现在
u
u
u 到
v
v
v 的简单路径上的点数(不包括
u
u
u 和
v
v
v ):
(1)
u
u
u 和
v
v
v 在同一个点双内:为所在的点双大小减
2
2
2 。
(2)
u
u
u 和
v
v
v 都是割点且不在同一点双:
u
u
u 到
v
v
v 的路径上(不包括
u
u
u 及
v
v
v )的点双大小之和(注:除
u
u
u 和
v
v
v 之外的割点只能被统计一次)。
(3)
u
u
u 和
v
v
v 不在同一点双并且都不是割点:
u
u
u 到
v
v
v 的路径上的所有点双大小之和减去(路径上的点双个数加一)。
…
综上,我们把方点的权值设为对应点双大小,圆点的权值为
−
1
-1
−1 ,那么可能出现在
u
u
u 到
v
v
v 的简单路径上的点数(不包括
u
u
u 和
v
v
v )就是圆方树
u
u
u 到
v
v
v 的路径上点的权值之和。
于是,我们把问题转化成一棵树上所有有序圆点对两两路径权值和之和。
状态:
f
[
u
]
f[u]
f[u] 表示
u
u
u 的子树内无序圆点对的路径权值和之和。
g
[
u
]
g[u]
g[u] 表示
u
u
u 到
u
u
u 的子树内所有圆点的路径权值和之和。
s
u
m
[
u
]
sum[u]
sum[u] 表示
u
u
u 的子树内圆点的个数。
v
a
l
[
u
]
val[u]
val[u] 表示
u
u
u 点的权值。
转移:
s
u
m
[
u
]
=
[
u
是
圆
点
]
+
∑
v
∈
s
o
n
[
u
]
s
u
m
[
v
]
sum[u]=[u是圆点]+\sum_{v\in son[u]}sum[v]
sum[u]=[u是圆点]+v∈son[u]∑sum[v]
g
[
u
]
=
∑
v
∈
s
o
n
[
u
]
{
g
[
v
]
+
s
u
m
[
v
]
×
v
a
l
[
u
]
}
g[u]=\sum_{v\in son[u]}\{g[v]+sum[v]\times val[u]\}
g[u]=v∈son[u]∑{g[v]+sum[v]×val[u]}
在枚举子树
v
v
v 的过程中记录下
g
[
u
]
′
g[u]'
g[u]′ 和
s
u
m
[
u
]
′
sum[u]'
sum[u]′ 表示
v
v
v 之前的子树(不包括
v
v
v )的 dp 值:
f
[
u
]
+
=
f
[
v
]
+
g
′
[
u
]
×
s
u
m
[
v
]
+
g
[
v
]
×
s
u
m
′
[
u
]
f[u]+=f[v]+g'[u]\times sum[v]+g[v]\times sum'[u]
f[u]+=f[v]+g′[u]×sum[v]+g[v]×sum′[u]
注意图可能不连通,所以答案为:
2
×
∑
i
是
某
个
连
通
块
的
根
f
[
i
]
2\times\sum_{i是某个连通块的根}f[i]
2×i是某个连通块的根∑f[i]
复杂度
O
(
n
+
m
)
O(n+m)
O(n+m) 非常优秀。
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define Edge2(u) for (int e = adj2[u], v; e; e = nxt2[e]) if ((v = go2[e]) != fu)
using namespace std;
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1e5 + 5, D = N << 1, M = D << 1;
int n, m, ecnt, nxt[M], adj[N], go[M], dfn[N], low[N], T, stk[N],
top, nm, ecnt2, nxt2[M], adj2[D], go2[M], sze[D], sum[D];
ll f[D], g[D], ans;
int Min(int a, int b) {return a < b ? a : b;}
void add_edge(int u, int v)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void add_edge2(int u, int v)
{
nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
nxt2[++ecnt2] = adj2[v]; adj2[v] = ecnt2; go2[ecnt2] = u;
}
void dfs(int u)
{
dfn[stk[++top] = u] = low[u] = ++T;
Edge(u)
if (!dfn[v])
{
dfs(v);
low[u] = Min(low[u], low[v]);
if (dfn[u] <= low[v])
{
nm++;
do
{
add_edge2(nm, stk[top--]);
sze[nm]++;
} while (stk[top + 1] != v);
add_edge2(nm, u); sze[nm]++;
}
}
else low[u] = Min(low[u], dfn[v]);
}
void dp(int u, int fu)
{
Edge2(u) dp(v, u);
sum[u] = u <= n; g[u] = u <= n ? sze[u] : 0;
Edge2(u)
f[u] += f[v] + g[u] * sum[v] + g[v] * sum[u],
g[u] += g[v] + 1ll * sum[v] * sze[u],
sum[u] += sum[v];
}
int main()
{
int i, x, y;
n = nm = read(); m = read();
For (i, 1, n) sze[i] = -1;
For (i, 1, m) x = read(), y = read(),
add_edge(x, y);
For (i, 1, n) if (!dfn[i])
dfs(i), dp(i, 0), ans += f[i] << 1;
cout << ans << endl;
return 0;
}