正睿提高組2017模擬題二T2
不會線性的,但是群裏有個大神,發現用可以用80分的復雜度寫出100分的效果,於是。。。。
考慮每次加入一條邊,我們用f[x][j]表示加入第i條邊後,當前的並查集x中,第j個點的父親。那麽如何加呢?假設當前第i條邊的兩端點為u和v,如果第x個並查集中u和v聯通那麽很明顯,它不可以再加到這個並查集中(每個並查集維護的就是一個極大森林),然後就往後找,直到能找到一個x使得其中u和v是不連通的為止(肯定能找到,因為如果已有的都已經連通,說明前x個極大森林中都無法刪去這條邊,所以再構建一個新的並查集就好了)然後將其中的u和v設為聯通的。最後你會發現那條邊被添加進了哪個並查集,它的答案就是並查集的標號。
但是我們不可能在添加邊的時候一個個找並查集吧,又可以發現如果第x個並查集中u和v是聯通的,那麽1~x-1個並查集中u和v也一定是聯通的(構建的方法所致),所以根據這個,我們就每次二分一下找到標號最小的可以將當前邊添加進去的並查集。
然後又有一個問題了,如果這麽開的話空間是f[m][n]的,這樣也不行。所以我們還要用deg[i]來記錄每個點的連接的邊數,假設deg[i]=x,那麽可以發現,i這個點只有可能在前x個並查集中與其他點聯通(要不然邊不夠),又因為有m條邊所以deg[i]的總和為2m,所以開上2m的空間就好了。
對,然後又有一個問題了,怎麽開呢,每一維的第二維大小都不同啊(這個我真的不會啊,發現別人是用指針的,於是看了老半天別人的代碼,又去研究了怎麽用指針。。。)
於是乎推薦一篇博客,裏面有寫怎麽用數組指針:http://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html
對,就這樣了,代碼的解釋之後再寫吧。。。
不過按照復雜度這只有80分!!!(不知道為什麽過了)
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cstdlib> #define maxn 1000009 using namespace std; int d[maxn],a[maxn*2],*f[maxn],*to=a,n,m,u[maxn],v[maxn]; typedef long long ll; inline int read() {char ch; int ex=0; while ((ch<‘0‘)||(ch>‘9‘)) ch=getchar(); while ((ch>=‘0‘)&&(ch<=‘9‘)) { ex=ex*10+ch-‘0‘; ch=getchar(); } return ex; } int getf(int dep,int x) { int now=x,k; while (*(f[now]+dep)!=now) now=*(f[now]+dep); while (*(f[x]+dep)!=now) k=*(f[x]+dep),*(f[x]+dep)=now,x=k; return now; } int main() { n=read();m=read(); for (int i=1;i<=m;i++) u[i]=read(),v[i]=read(),d[u[i]]++,d[v[i]]++; for (int i=1;i<=n;i++) { f[i]=to; to+=d[i]; for (int j=1;j<=d[i];j++) *(f[i]+j)=i; d[i]=1; } for (int i=1;i<=m;i++) { int l=1,r=min(d[u[i]],d[v[i]]),mid,ans=0; d[u[i]]++;d[v[i]]++; while (l<=r) { mid=(l+r)>>1; if (getf(mid,u[i])==getf(mid,v[i])) l=mid+1; else { ans=mid; r=mid-1; } } *(f[getf(ans,u[i])]+ans)=getf(ans,v[i]); printf("%d\n",ans); } return 0; }
正睿提高組2017模擬題二T2