【题目链接】
【思路要点】
- 首先我们来证明点双连通分量的一个性质。
- 引理:在一个点双连通分量中,给定任意三个不同的点\(a\),\(b\),\(c\),一定存在一条从\(a\)到\(c\)的,经过每个点至多一次的简单路径经过了\(b\)。
- 证明:考虑网络流。在原图中存在无向边的点对之间建立无向边,容量为1;对每个点拆点限流,容量为1;由\(a\)和\(c\)向汇点连边,容量为1;由源点向\(b\)连边,容量为2。显然,原命题等价于源点到汇点的最大流等于2。显然源点到汇点的最大流不超过2,现在我们来证明它大于1。考虑最小割,我们发现割去一条点对之间的无向边等价于删去原图中的一条边,割去一条限流边等价于删去原图中的一个点,由于原图点双连通,我们无法只割去一条容量为1的边使得源点到汇点不连通,因此最小割大于1,原命题得证。
- 那么,我们建立圆方树,枚举\(b\),考虑如何计算合法的\((a,c)\)点对数。
- 由上面证明的引理,我们只需要保证\(a\)到\(b\)的路径上和\(c\)到\(b\)的路径上没有相同的必经的圆点就可以保证\((a,b,c)\)是一个合法的点对。也即在直接与\(b\)由方点相连的圆点的子树中,\(a\)和\(c\)应当属于不同的子树。
- 简单树形DP即可。
- 时间复杂度\(O(N+M)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 200005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } vector <int> a[MAXN], b[MAXN]; int n, m, oldn, top, Stack[MAXN]; int timer, dfn[MAXN], low[MAXN]; int cnt, size[MAXN]; long long ans, tans[MAXN]; void work(int pos, int fa) { size[pos] = pos <= oldn; for (unsigned i = 0; i < b[pos].size(); i++) if (b[pos][i] != fa) { work(b[pos][i], pos); size[pos] += size[b[pos][i]]; } if (pos <= oldn) { ans += (cnt - 1ll) * (cnt - 1ll); for (unsigned i = 0; i < b[pos].size(); i++) if (b[pos][i] != fa) ans -= tans[b[pos][i]]; } else { for (unsigned i = 0; i < b[pos].size(); i++) if (b[pos][i] != fa) tans[pos] += 1ll * size[b[pos][i]] * size[b[pos][i]]; long long tmp = 1ll * (cnt - size[pos]) * (cnt - size[pos]); for (unsigned i = 0; i < b[pos].size(); i++) if (b[pos][i] != fa) ans -= tans[pos] - 1ll * size[b[pos][i]] * size[b[pos][i]] + tmp; } } void tarjan(int pos) { Stack[++top] = pos; cnt++; dfn[pos] = low[pos] = ++timer; for (unsigned i = 0; i < a[pos].size(); i++) if (dfn[a[pos][i]] == 0) { tarjan(a[pos][i]); chkmin(low[pos], low[a[pos][i]]); if (low[a[pos][i]] == dfn[pos]) { int tmp = 0, cmt = 1; n++; while (tmp != a[pos][i]) { tmp = Stack[top--]; b[n].push_back(tmp); b[tmp].push_back(n); cmt++; } b[n].push_back(pos); b[pos].push_back(n); } } else chkmin(low[pos], dfn[a[pos][i]]); } int main() { read(n), read(m), oldn = n; for (int i = 1; i <= m; i++) { int x, y; read(x), read(y); a[x].push_back(y); a[y].push_back(x); } for (int i = 1; i <= oldn; i++) if (dfn[i] == 0) { cnt = 0; tarjan(i); work(i, 0); } writeln(ans); return 0; }