[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]);
}
posted @ 2018-11-11 22:04  hankeke303  阅读(141)  评论(0编辑  收藏  举报