[BZOJ2054]疯狂的馒头 并查集
写点题庆祝一下自己的退役。
其实是为了学习一下并查集。
之前校内NOIP模拟赛(别说了,NOIP已经刮掉了)有一道题可以用并查集做,但是被我用强力剪枝水过去了。
今天发现并查集好像是个非常好用的东西,似乎还挺强大的,所以来学习一下。
突然发现并查集这东西好像有很多好的性质,要是明年的今天没有退役的话,或许可以来出些题目。
看到这种题如果暴力用线段树做的话那么就是显然的\(O(m\log n)\)显然做不了。
那么考虑时间倒流,先染后面的色,这样如果染过色了的点就不需要再染了。
那么我们现在就是需要想个办法保证,每个该被染色的点,被且只被染一次色,这样才能有复杂度保证。
这里介绍一个新的技巧。
我们维护一个东西,表示这个点右边(含自己)未被染色的第一个点\(fa[x]\)。有了这个东西我们就可以直接跳过之间那些被染过色的点。
考虑染完色以后,他的\(fa[x]\)的值应该是和\(fa[x+1]\)一样的。但是,只要\(fa[x+1]\)的值被改掉了,\(fa[x]\)也应该一起改。
发现这种东西可以种并查集维护。
然后具体实现看代码吧。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define REP(i,a,n) for(register int i(a);i<=(n);++i)
#define PER(i,a,n) for(register int i(a);i>=(n);--i)
#define dbg(...) fprintf(stderr,__VA_ARGS__)
template<typename A,typename B>inline char SMAX(A&a,const B&b){return a<b?a=b,1:0;}
template<typename A,typename B>inline char SMIN(A&a,const B&b){return a>b?a=b,1:0;}
const int SZ=(1<<21)+1;char obuf[SZ+128],*oS=obuf,*oT=obuf+SZ-1;
inline void flush(){fwrite(obuf,1,oS-obuf,stdout);oS=obuf;}
#define printf(...) oS>oT&&(flush(),1),oS+=sprintf(oS,__VA_ARGS__)
struct IO_flusher{inline~IO_flusher(){flush();}}io_flusher;
typedef long long ll;
const int N=1e6+7,M=1e7+7;
int n,m,p,q,col[N];
int fa[N],stk[N];
inline int Find(int x){while(fa[x]!=x)stk[++stk[0]]=x,x=fa[x];while(stk[0])fa[stk[stk[0]--]]=x;return x;}
int main(){
scanf("%d%d%d%d",&n,&m,&p,&q);
REP(i,1,n)fa[i]=i;fa[n+1]=n+1;//错误笔记:Very important!!! 必须要把n+1自己存一下
PER(i,m,1){
int l=((ll)i*p+q)%n+1,r=((ll)i*q+p)%n+1;if(l>r)std::swap(l,r);
for(register int j=Find(l);j<=r;j=Find(j))col[j]=i,fa[j]=j+1;//错误笔记:这里不能写Union(j,j+1),因为这里要保证一定是指向右边的,如果按秩合并就会出问题。
}
REP(i,1,n)printf("%d\n",col[i]);
}