1. 程式人生 > 其它 >cf 1559(div2)

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