1. 程式人生 > 其它 >Codeforces Round #738 (Div. 2) D2題解

Codeforces Round #738 (Div. 2) D2題解

D2. Mocha and Diana (Hard Version)

至於D1,由於範圍是1000,我們直接列舉所有的邊,看看能不能加上去就行,複雜度是\(O(n^2logn)\).至於\(n\)到了\(1e5\),就要重新考慮解法。
考慮到樹的邊數是\(n-1\)。也就是說我們列舉的大多數邊都是無效的,這個時候就要考慮我們能不能只連有效的邊,對於兩個連通塊而言,顯然在這兩個連通塊之間我們只需要連一條邊就可以了。還有一般這種兩個圖的題,一般都是在一個圖中操作,在另一個圖中進行合不合法的判定。這裡我們以第一個圖作為操作的物件,以第二個圖作為判定的依據。首先想到的就是我們先將兩個圖都劃分連通塊,然後列舉第一個圖任意兩個連通塊x,y,考慮兩個連通塊的點,若存在u屬於x,v屬於y並且u和v在圖二中不屬於同一個連通塊,那麼u和v就可以相連,這兩個連通塊就可以聯通。並且兩個連通塊不能連的充要條件是這兩個連通塊內的所有點在圖二中都屬於同一個連通塊內。列舉任意兩個連通塊顯然複雜度是過不去的,我們能不能固定一個連通塊(比如說連通塊1),然後嘗試將其他連通塊與它聯通,但這樣會不會少連一些邊?比如連通塊x與連通塊y都不能與1聯通,但這兩個能不能聯通呢?考慮下剛才提到的充要條件,這兩個連通塊不能和1聯通,說明這兩個連通塊內的所有點和1的所有點在圖二中都屬於同一個連通塊內。那這兩個連通塊的點在圖二也都屬於同一個連通塊內啊!肯定不能連啊。好了,那我們固定一號連通塊,嘗試將其他的所有連通塊與它聯通。顯然我們找點的時候我們肯定不能列舉所有的點來判斷,既然要求找到一個點對即可。我們考慮儲存下當前一號連通塊內的所有點在圖二中的連通塊編號,以及該編號中的其中一個點(方便輸出方案)。當下一個連通塊來的時候我們直接列舉這個連通塊內的所有點來判斷是否有沒有符合要求的點對。並且當可以聯通時,我們還需要在圖二中連邊,合併兩個連通塊,這個可以用並查集實現,但有時這兩個連通塊(符合要求的點對在圖二中的兩個連通塊)都在我們維護的一號連通塊內(圖一中),這就需要我們維護的東西支援刪除操作,這個我用了set,不想再動腦了。你以為這就結束了嗎?不不不....我們發現當一號連通塊內對應的在圖二中的連通塊個數越多,越容易與其它連通塊聯通,為了避免一些噁心情況(因為當前1號連通塊對應的圖二中的連通塊個數為1,而失敗聯通,但之後有其他的連通塊對應圖二有更多的連通塊,調整下順序就能聯通原本不能聯通的),我們將圖一中的連通塊按照在圖二中對應地連通塊的個數排序,個數多著在前,這樣我們就先儘可能的增加我們維護的一號圖在二號圖中的連通塊的個數,之後就能儘可能多的接納其他的連通塊。總複雜度\(O(nlogn)\)

,多少次想放棄,但還是想堅持下去....

檢視程式碼

//不等,不問,不猶豫,不回頭.
#include
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair
#define PII pair
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=1e5+10;
int n,m1,m2,f[3][N],vis[N],num,bx[N],by[N],id[N];
struct wy{int len,v;}a[N];
vectorv[N];
mapmp;
sets;

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

inline int getf(int op,int x){return f[op][x]==x?x:f[op][x]=getf(op,f[op][x]);}

inline void init()
{
get(n);get(m1);get(m2);
rep(i,1,n) f[1][i]=f[2][i]=i;
rep(i,1,m1)
{
int get(x),get(y);
int t1=getf(1,x),t2=getf(1,y);
if(t1!=t2) f[1][t1]=t2;
}
rep(i,1,m2)
{
int get(x),get(y);
int t1=getf(2,x),t2=getf(2,y);
if(t1!=t2) f[2][t1]=t2;
}
}

inline bool cmp(wy a,wy b) {return a.len>b.len;}

inline void prework()
{
rep(i,1,n) f[1][i]=getf(1,i),f[2][i]=getf(2,i);
rep(i,1,n)
{
if(!vis[f[1][i]])
{
vis[f[1][i]]=1;
id[f[1][i]]=++num;
}
a[id[f[1][i]]].len++;
a[id[f[1][i]]].v=f[1][i];
}
sort(a+1,a+num+1,cmp);
rep(i,1,num) id[a[i].v]=i;
rep(i,1,n) v[id[f[1][i]]].push_back(i);
memset(vis,0,sizeof(vis));
for(auto x:v[1])
{
if(vis[f[2][x]]) continue;
vis[f[2][x]]=1;
s.insert(f[2][x]);
mp[f[2][x]]=x;
}
}

inline void solve()
{
int ans=0;
rep(i,2,num)//列舉從第二個連通塊開始的所有連通塊,嘗試與第一個連通塊合併。
{
bool flag=false;
for(auto x:s)//列舉每個連通塊
{
if(flag) break;
for(auto y:v[i])
{
int t=getf(2,y);
if(t!=x&&!flag)
{
bx[++ans]=mp[x];
by[ans]=y;
f[2][t]=x;
if(s.find(t)!=s.end()) s.erase(t);
flag=true;
}
else if(flag&&!vis[t])
{
vis[t]=1;
s.insert(t);
mp[t]=y;
}
}
}
}
put(ans);
rep(i,1,ans) printf("%d %d\n",bx[i],by[i]);
}

int main()
{
// freopen("1.in","r",stdin);
//freopen("sol.out","w",stdout);
init();
prework();
solve();
return (0_0);
}
//以吾之血,鑄吾最後的亡魂.