圆方树是什么?
其实很简单,就是点双的缩点形式。原图中的点用圆点表示,对于每个点双内部建立一个方点,并把这个方点向点双内所有点连边。对于一个单点,就把它连出去的每条边都改为方点并和原来这条边两端的节点连接。这样做之后,任意两个圆点以及任意两个方点都不相邻。
接下来看题。
传送门
建立圆方树,方点权值为其代表的点双内的点数,如果这个方点是从一条边变过来的,那么其权值为2,同时令圆点权值为-1,这样,原图中选两个点作为起点和终点的方案数就等于这两个点在圆方树中一条链上所有点的权值和。
所以我们对每个点单独统计贡献就好了,注意只在作为中间的点的时候有贡献,作为两端的时候没有贡献。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <stack>
typedef long long ll;
const int maxn = 2e5 + 7;
const int maxm = 4e5 + 7;
using namespace std;
int n, m, cnt, sum;
int dfn[maxn], id;
int low[maxn];
int val[maxn];
int size[maxn];
ll ans;
struct graph {
int to[maxm];
int nex[maxm];
int last[maxn], k;
inline void add_edge(int x, int y)
{
to[++k] = y; nex[k] = last[x]; last[x] = k;
}
} g1, g2;
stack <int> q;
inline int read()
{
int X = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') X = X * 10 + ch - '0', ch = getchar();
return X;
}
void tarjan(int x)
{
dfn[x] = low[x] = ++id;
val[x] = -1;
size[x] = 1;
q.push(x);
for (int i = g1.last[x]; i; i = g1.nex[i]) {
int y = g1.to[i];
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
if (low[y] >= dfn[x]) {
cnt++;
int tmp = 0, num = 1;
while (tmp != y) {
tmp = q.top();
q.pop();
num++;
size[cnt] += size[tmp];
g2.add_edge(cnt, tmp);
}
size[x] += size[cnt];
val[cnt] = num;
g2.add_edge(x, cnt);
}
}
else low[x] = min(low[x], dfn[y]);
}
}
void dfs(int x)
{
if (x <= n) ans -= 1ll * (sum - 1);
ans += 1ll * size[x] * (sum - size[x]) * val[x];
for (int i = g2.last[x]; i; i = g2.nex[i]) {
int y = g2.to[i];
ans += 1ll * size[y] * (sum - size[y]) * val[x];
dfs(y);
}
}
int main(void)
{
n = cnt = read();
m = read();
while (m--) {
int x = read(), y = read();
g1.add_edge(x, y);
g1.add_edge(y, x);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i), sum = size[i], dfs(i);
cout << ans;
return 0;
}