cf 1559(div2)
比賽連結:https://codeforces.com/contest/1559
因為一些不良程式碼習慣,前期做得慢了;掉分T_T
D2
題意:
給兩個森林,要求加相同的邊,求在兩邊都不生成環的情況下最多能加多少條邊,並輸出能加的邊。
分析:
可以用set,較暴力地做。
首先,左邊森林和右邊森林都各自有一些連通塊。當我們想要在兩點之間連邊,需要保證它們在左右兩邊都不在同一個連通塊裡。
最終狀態一定是某一邊的森林變成了一棵樹(原因下面細講)——由於加邊是同時的,所以變成樹的森林是一開始連通塊就比較少的那個森林。下面我們把它作為左邊森林。
如果我們在連線左邊的兩個連通塊時,能知道這兩個連通塊裡包含的右邊連通塊的情況,並且選出兩個在右邊不是同一個連通塊的兩個點,連邊,就好了。
為了方便想,把左邊的連通塊成為“行”,右邊的連通塊成為“列”,行列相交的格子裡存放的是同時在兩個連通塊裡的點。
我們用一個map來存格子的“代表點”:mp[a][b]=p表示用p這個點來代表同時在左邊a連通塊、右邊b連通塊的那些點。
然後,我們給每一行開一個set,裡面存放它的點涉及到的列;給每一列開一個set,裡面存放它的點涉及到的行。
這樣,當我們想要合併兩行時,可以從它們的set中取出兩個不同的列,通過map找到對應的兩個代表點,然後把它們連邊。
問題是,這兩行的set中一定有兩個不同的元素嗎?萬一所有的元素都是相同的怎麼辦?
這需要我們再安排一個合併的順序——將行按set的大小排序,每次取最大的兩個合併。如果最大的那個行的set也只有一個元素,那麼所有行的set都只有一個元素;但是前面我們選擇讓連通塊較少的森林作為左邊森林,也就是行數\(<\)列數;所以這種情況一定是行數\(=\)列數,那麼每一行的set裡存的元素就是彼此不同的。如果最大的那個行有兩個及以上元素,那必然可以找到一個和次大的行中第一個元素不同的元素。
所以我們每次一定能在兩行的set中找到符合條件的兩個元素,對應了左邊森林的兩個連通塊和右邊森林的兩個連通塊。通過map可以找到同時在左右對應連通塊中的代表點,把兩個代表點連邊即可。
這個過程也告訴我們,最終狀態一定是左邊森林只剩下了一個連通塊,也就是變成了一棵樹。
接著,連邊了以後我們需要合併兩行和兩列——這裡當然是把小的往大的合併。需要改變的資訊是set和map:把被合併者set中的每個元素都放進合併者的set裡,並且在那個元素的set裡把被合併者刪除,加入合併者;對應被合併者的map也要改成對應合併者。時間複雜度\(O(nlog^2(n))\)。
當然,我們區分各行和各列是用並查集的。
程式碼如下:
#include<iostream> #include<map> #include<set> #include<algorithm> #define mkp make_pair using namespace std; int const N=1e5+5; int n,m1,m2,fa[3][N],ans,pl[N],pr[N],t1,t2; set<int>row[N],col[N]; set<pair<int,int> >rows; map<int,int>mp[N]; int rd() { int ret=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return ret*f; } int find(int t,int x) { if(fa[t][x]!=x)fa[t][x]=find(t,fa[t][x]); return fa[t][x]; } void merge1(int x,int y) { for(int it:row[y]) { row[x].insert(it); col[it].erase(y); col[it].insert(x); mp[x][it]=mp[y][it]; } } void merge2(int x,int y) { for(int it:col[y]) { col[x].insert(it); row[it].erase(y); row[it].insert(x); mp[it][x]=mp[it][y]; } } int main() { n=rd(); m1=rd(); m2=rd(); t1=1; t2=2; for(int t=1;t<=2;t++) for(int i=1;i<=n;i++)fa[t][i]=i; for(int i=1,x,y;i<=m1;i++) { x=rd(); y=rd(); x=find(1,x); y=find(1,y); fa[1][x]=y; } for(int i=1,x,y;i<=m2;i++) { x=rd(); y=rd(); x=find(2,x); y=find(2,y); fa[2][x]=y; } if(m1<m2)swap(t1,t2); for(int i=1,p1,p2;i<=n;i++) { p1=find(t1,i); p2=find(t2,i); row[p1].insert(p2); col[p2].insert(p1); mp[p1][p2]=i; } for(int i=1;i<=n;i++) if(find(t1,i)==i)rows.insert(mkp(-row[i].size(),i)); while(rows.size()>1) { int x=(*rows.begin()).second; rows.erase(rows.begin()); int y=(*rows.begin()).second; rows.erase(rows.begin()); int a=*row[x].begin(); int b=*row[y].begin(); if(a==b) { set<int>::iterator it=row[x].begin(); it++; a=*it; } ans++; pl[ans]=mp[x][a]; pr[ans]=mp[y][b]; if(col[a].size()<col[b].size())swap(a,b); merge1(x,y); merge2(a,b); rows.insert(mkp(-row[x].size(),x)); } printf("%d\n",ans); for(int i=1;i<=ans;i++)printf("%d %d\n",pl[i],pr[i]); return 0; }View Code