不会线性的,但是群里有个大神,发现用可以用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; }