传送门
相较于D1的\(n^2\)暴力。这次肯定不行了。这时我们就要想如何快速合并2片森林中所有的树呢。
首先可以加完边后两片森林依旧为森林,因此最多可以加的边数为边数多的那片森林确定。这样我们不妨令边数多的森林为第一片森林,可以加的最多的边即把第一片森林搞得只剩下一棵树。
在第一片森林中两棵树合并时,两颗树需要分别提供一个连接点,并且这两个连接点在第二篇森林中不属于同一棵树。那么这两颗树可以通过这两个连接点来连接。
这个连接点是必然存在的,这里简略证明一下(口胡开始): 对于第一片森林中的两棵树,假设不存在这一连接点,那么即这两棵树上的点在第二片森林中属于同一棵树,此时有一条边的差异这是关键,后面口胡,那么在第一片森林中必然有额外的一点属于这两棵树中的一个,而又不属于第二片森林中的那棵树,假设不成立,因此必然存在这个连接点。(口胡结束)
之后我们该如何找这两个点呢?这是不妨逆向一下,我们来看在第一片森林的第\(i\)棵树如果要提供连接点位于第二片森林的第\(j\)棵树时,可以提供的点是谁,这里可以用二维map来储存。即\(mp[u][v]=i\)表示点\(i\)同时位于第一片森林的\(u\)树和第二片森林的\(v\)树,
这样,我们在合并第一片森林中的两棵树时,只要确定两棵树连接点分别属于第二片森林中哪棵树,只要保证不属于第二片森林中的同一棵树,就可以立即得到连接点(这里的连接点可以有多个,但我们只需要记住一个就行了),不用再枚举,这里我们只需要预处理出上面的\(mp\)数组,可以用\(stl\)的\(map\)储存。
然后合并时,将小的往大的合并,枚举小的,合并后同时更新第一片森林中新树的信息。同时我们看到在接上边后,第二片森林的连接情况也会有所变化,并且二者的联系\(mp[u][v]\)也会变化,因此我们需要同时存储第一片森林中的树能到的第二片树林中的哪些树 和 第二片森林能到第一片森林中的哪些树。
在合并时,假设第一片森林中两棵树为\(u,v\),其在第二片森林中的所属树分别为\(a,b\),这里保证\(a!=b\),然后合并时,将小的往大的合并,假设\(u\)树大于\(v\)树,则所有的\(v\)能到达的第二片森林的树的信息即\(mp\)数组合并到\(u\),即\(mp[u][s1(v)]=mp[v][s1(v)]\),同时删除\(s2(s1(v))中的点v并添加点u\),同理假设 \(a\)树大于\(b\)树,\(mp[s2(a)][a]=mp[s2(b)][b]\)删除在\(s1(s2(b))中的点b并加入a\)。
那么这里时间复杂度相当于\(x\)个数,每次选两两合并为一个数,代价为较小的那个数,当合并只剩下最后一个数时,最小的代价是多少,此时,当每次把最大的固定,把其他的往里面合并时,代价为线性复杂度.
但我们只能保证在第一片森林中是线性合并,在第二片森林中,我们只满足把小的向大的合并,这里的复杂度最坏是\(O(nlogn)\),满足最坏时的情况即类似线段树的情况,
此时合并的次数为\(\sum_{i=1}^{logn} \frac{n}{2^i}*2^{i-1}=\sum_{i=1}^{logn} \frac{n}{2}\)
是\(O(nlogn)\)级的,加上stl的\(O(logn)\)的复杂度,总算法复杂度\(O(n log^2n)\)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<map> #include<vector> #include<set> #include<cmath> using namespace std; #define rep(i,a,b) for(int i=a;i<=b;++i) #define rpe(i,a,b) for(int i=a;i>=b;--i) #define pts putchar('\n') #define ptc putchar(' ') #define pb push_back typedef long long ll; typedef unsigned long long ull; typedef pair<int,int>P; const int inf=0x7f7f7f7f; const ll linf=1e18; const int maxn=1e5+9; const int maxm=1e7+9; const double PI=3.1415926; const double eps=1e-5; const ll mod=998244353; const int base=131; const int N=1e6; namespace IO{ ll read(){ ll a=1,b=0;char c=getchar(); while(c>'9'||c<'0'){if(c=='-')a=-1;c=getchar();} while(c>='0'&&c<='9'){b=(b<<3)+(b<<1)+c-'0';c=getchar();} return a*b ; } void print (ll x){ if(x<0) putchar('-'),x=-x; if(x>9) print(x/10); putchar(x%10+'0'); } } using namespace IO; int n,m1,m2; int fa1[maxn],fa2[maxn]; map<int,int>mp[maxn]; set<int>s1[maxn],s2[maxn]; vector<P>s,ans; set<int>::iterator it1,it2,it; int cnt[maxn]; int find(int *fa,int x){ return x==fa[x]? x:fa[x]=find(fa,fa[x]); } void Merge(int x,int y){ for(it=s1[y].begin();it!=s1[y].end();++it){ mp[x][*it]=mp[y][*it]; s1[x].insert(*it); s2[*it].insert(x); s2[*it].erase(y); } } void Merge_(int x,int y){ for(it=s2[y].begin();it!=s2[y].end();++it){ mp[*it][x]=mp[*it][y]; s2[x].insert(*it); s1[*it].insert(x); s1[*it].erase(y); } } int main(){ n=read(),m1=read(),m2=read(); rep(i,1,n) fa1[i]=fa2[i]=i; int u,v; rep(i,1,m1){ u=read(),v=read(); fa1[find(fa1,u)]=find(fa1,v); } rep(i,1,m2){ u=read(),v=read(); fa2[find(fa2,u)]=find(fa2,v); } if(m1<m2) swap(fa1,fa2); rep(i,1,n){ u=find(fa1,i);v=find(fa2,i); mp[u][v]=i; cnt[u]++; s1[u].insert(v); s2[v].insert(u); } rep(i,1,n){ if(find(fa1,i)==i) s.pb(P(-cnt[i],i)); } sort(s.begin(),s.end()); int len=s.size(); u=s[0].second; rep(i,1,len-1){ v=s[i].second; it1=s1[u].begin(); it2=s1[v].begin(); if( (*it1) == (*it2) ) it1++; ans.pb(P(mp[u][*it1],mp[v][*it2])); Merge(u,v); if(s2[*it1].size() < s2[*it2].size()) swap(it1,it2); Merge_(*it1,*it2); fa1[find(fa1,u)]=find(fa1,v); } print(ans.size());pts; for(auto x:ans){ print(x.first);ptc;print(x.second);pts; } return 0; }