題目鏈接:點擊查看
題目大意:給出一張有 n 個點和 m 條邊的圖,每個點都有一個種類,共有 k 個種類,現在要從 k 個種類中每次選出兩種,對所有?C( k , 2 ) 種組合單獨討論,對于選出的兩個種類中,包含的所有的點以及其連邊所組成的子圖,如果該子圖可以拆分成二分圖,那么答案加一
題目分析:參考博客:https://blog.csdn.net/Zenith_Habitant/article/details/109451857
k 的范圍很大,正難則反,考慮計算不合法的組合個數,然后由總的合法個數減去就是答案了
首先看到題目中的二分圖,不難想到其定義為:“不含有奇環的一個圖”,而判斷二分圖的兩種方法,一種是直接 dfs 染色,另一種方法就是對每個點建立虛點然后并查集維護,可以參考:CH - 4901 關押罪犯
因為總的邊數只有 m 條邊,換句話說,假設每條邊 ( x , y ) 連接的 x 和 y 都屬于不同的種類,最終也只有 m 種組合方案,所以非法的種類數最多只有 m 種
所以對于每條邊 ( x , y ) 分類討論,設 type[ x ] 是 x 的種類:
如果 type[ x ] == type[ y ],直接用并查集將其合并即可如果 type[ x ] != type[ y ],將其加入所有,連接( type[ x ] , type[ y ] ) 這兩個種類的邊的集合中去
然后對于每個種類自己單獨的連通塊內,如果出現了奇環,說明當前這個種類無論和哪個組合匹配,都不可能產生貢獻了,對于這些種類單獨拿出來即可
然后就是對合并任意兩個種類的那些邊分組,依次合并,看看這兩個種類組合的話是否會產生貢獻,這里需要注意的是,因為對于合并每兩個組的邊集都是相互獨立的,所以在進行完一次 “ 合并 ”,檢查出答案后,需要及時撤銷,這樣就可以保證算法的正確性了,關于撤銷可以直接用并查集的撤銷來實現
代碼:
?
//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=1e6+100;map<pair<int,int>,vector<pair<int,int>>>mp;//map[{type_a,type_b}]={(u1,v1),(u2,v2)...(uk,vk)}//表示連接兩個組的邊 struct revo
{int fax,fay;int rkx,rky;
};int a[N],b[N],f[N],rk[N],type[N],tot;bool fail[N];stack<revo>st;int find(int x)
{return f[x]==x?x:find(f[x]);
}void merge(int x,int y)
{int xx=find(x),yy=find(y);revo temp;temp.fax=xx,temp.fay=yy;temp.rkx=rk[xx],temp.rky=rk[yy];st.push(temp);if(rk[xx]>rk[yy])swap(xx,yy);f[xx]=yy;rk[yy]=max(rk[yy],rk[xx]+1);
}void revocation(int k)
{while(k--){revo node=st.top();st.pop();f[node.fax]=node.fax;f[node.fay]=node.fay;rk[node.fax]=node.rkx;rk[node.fay]=node.rky;}
}void init()
{for(int i=1;i<N;i++){f[i]=i;rk[i]=1;}
}int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);init();int n,m,k;LL ans=0;scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;i++)scanf("%d",type+i);for(int i=1;i<=m;i++)scanf("%d%d",a+i,b+i);for(int i=1;i<=m;i++)//同一種類的邊,直接維護即可 {if(type[a[i]]==type[b[i]]){int xx=find(a[i]),yy=find(b[i]);if(xx!=yy)merge(a[i],b[i]+n),merge(b[i],a[i]+n);elsefail[type[a[i]]]=true;}}for(int i=1;i<=m;i++)//連接兩個不同種類的邊,將其歸類 {if(type[a[i]]==type[b[i]])continue;if(fail[type[a[i]]]||fail[type[b[i]]])continue;if(type[a[i]]>type[b[i]])swap(a[i],b[i]);mp[make_pair(type[a[i]],type[b[i]])].emplace_back(a[i],b[i]);}for(auto node:mp)//node.second=vector<pair<int,int>>{int cnt=0;for(auto it:node.second)//it=pair<int,int>=(u,v){int x,y;tie(x,y)=it;int xx=find(x),yy=find(y);if(xx!=yy)merge(x,y+n),merge(y,x+n),cnt+=2;else{ans--;break;}}revocation(cnt);}LL cnt=0;for(int i=1;i<=k;i++)if(!fail[i])cnt++;ans+=cnt*(cnt-1)/2;printf("%lld\n",ans);return 0;
}
?
總結
以上是生活随笔為你收集整理的CodeForces - 1445E Team-Building(可撤销并查集)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。