Network(思維-隨機數)

題意描述:

給出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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章