題意描述:
給出N個點,M條邊的無向連通圖,求移除兩條邊後使得該圖不連通的方案數。
N <= 2000 , M <= 100000
解題思路:
取一個點爲根進行dfs,得到一顆dfs樹,標記樹邊,那麼非樹邊只存在返祖邊(u和v爲祖先-子節點關係)
用 sum[u] 表示u節點子樹中越過u的返祖邊的數量(到達u不算越過u)。
用 sta[u] 表示u節點子樹中越過u的返祖邊的集合。
1. 很明顯橋的sum[u] = 0, 記橋的數量爲C,則橋的答案貢獻爲 C*(m-1) - C*(C-1)/2 。(選擇橋,圖已經不連通,另外一條邊有m-1種方案,再去重選擇兩條橋的方案就是貢獻)
2. 若有節點的sum[u] = 1,則子樹只有一條返祖邊越過了u,那麼選擇u的父親邊和這條返祖邊可以貢獻+1答案。
3. 若有若干個節點的返祖邊狀態完全相同,sta[u1] = sta[u2] = ... = sta[uk] ,則貢獻k*(k-1)/2個答案,如下圖所示,圖中u1和u2的 sta[u] 都是{a,b},集合大小爲2,答案貢獻爲2*(2-1)/2 = 1。(在sta[u]相等的集合中任選兩個節點,刪去他們的父親邊都能使得中間部分與圖不連通,sta[u]可通過給每條返祖邊賦一個隨機值來保存狀態)
代碼:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 400010;
int n, m, fa[N], dep[N], sum[N];
ll sta[N], ans = 0, C = 0;
struct node{
int v,next,used; //used表示該邊是否爲dfs樹邊
}e[N];
int head[N],cnt;
void add(int u,int v){
e[cnt].v = v, e[cnt].next = head[u], head[u] = cnt++;
}
void dfs(int u,int f,int d){ //構造dfs樹
dep[u] = d;fa[u] = f;
for(int i = head[u];~i;i = e[i].next){
int v = e[i].v;
if(v == f || dep[v])continue;
dfs(v,u,d+1); e[i].used = 1;
}
}
void dfs2(int u){ //樹標記累加和
for(int i = head[u];~i;i = e[i].next){
int v = e[i].v;
if(e[i].used == 0)continue;
dfs2(v);
sum[u] += sum[v], sta[u] ^= sta[v];
}
ans += (sum[u] == 1);
C += (sum[u] == 0);
}
map<ll,int>mp;
int main(){
memset(head,-1,sizeof head);
scanf("%d %d",&n,&m);
for(int i = 1;i<=m;i++){
int u, v;
scanf("%d %d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,-1,1);
for(int i = 0;i<m;i++){
if(e[i<<1].used || e[i<<1|1].used)continue;
int u = e[i<<1].v, v = e[i<<1|1].v;
if(dep[u] > dep[v])swap(u,v);
ll tt = (ll)rand()*(rand()+2);
sum[u] --, sum[v] ++;
sta[u] ^= tt, sta[v] ^= tt;
}
dfs2(1);
for(int i = 2;i<=n;i++)
if(sta[i])mp[sta[i]]++;
for(auto &k:mp)
ans += (ll)k.second*(k.second-1)/2;
C --; //橋的數量,根節點沒有父親邊應減去1
ans += C*(m-1) - C*(C-1)/2;
printf("%lld\n",ans);
return 0;
}