疯狂的涂色
小t非常喜爱画画,但是他还是一个初学者。他最近费尽千辛万苦才拜到小Q为师。小Q是画鸡蛋长大的,让小t一入门就拿着一张白纸条疯狂地涂色。假设纸条被划分成了n个区域,用1~n的整数从左到右顺序编号,小Q总共下达了m条指令。第i条指令是让小t把编号为(i*p+q)modn+1与(i*q+p)mod n+1(p,q为常整数)之间的区域(连续的一段区域)涂成第i种颜色。
现在由于小Q下达的指令过多,小t一时应付不过来。小Q只让他回答每一个区域最后的颜色。趁小Q还在“五谷轮回之所”忙碌时,小t偷偷的请让你这个计算机高手帮他算出最后的颜色状态,并告诉他。
输入格式
文件仅一行,为四个整数n,m,p,q。
输出格式
文件共n行,第i行代表最后第i个格子的颜色。白色编号为0。
输入样例
4 32 4
输出样例
2
2
3
0
数据范围
20%数据满足:1≤n≤1000,1≤m≤10000;
40%数据满足:1≤n≤10000,1≤m≤100000;
100%数据满足:1≤n≤1000000,1≤m≤10000000;1≤m*p+q,m*q+p≤231-1;
这道题中,左右界周期性,所以需要找循环节,可以降低时间复杂度。。。这个最恼人了,到现在还没有掌握,虽然打出来了。
这道题真的有点难。除了考虑循环节之外,还要考虑一个尽量不重复刷。
因此本题正解是,从最后一刷往前枚举。因为只需要知道最后颜色状态,因此只需要刷最后一段,上一个周期重复的就不再刷。
倒着刷,就保证了先刷的是要保留的,因此后面遇到就不能再刷了,这个用一个并查集来维护,跟前段时间覃禹舜讲的一道题很相似,
也是用并查集维护下一个需要处理的点,(这种特殊的并查集必须要让旧点的父亲指向新点)。
另外这道题有点变态的地方,如果写普通的并查集要栈溢出,深度过大。因此要写一个手工的栈,模拟递归。。。
long t;
while (fa[a]!=a)
{
stack[++top] = a;
a=fa[a];
}
t = fa[a];
这部分递归求根,
while (top>0)
{
a = stack[top];
top--;
fa[a] = t;
}
return t;
这部分回溯压缩树的深度。
//#include <iostream>
//using std::cout;
//using std::cin;
#include <cstdio>
const long oo = 0x7fff0000;
long c[1000002];
long fa[1000002];
long stack[1000002];
long top = 0;
long n;long m;long p;long q;
long getroot(long a)
{
long t;
while (fa[a]!=a)
{
stack[++top] = a;
a=fa[a];
}
t = fa[a];
while (top>0)
{
a = stack[top];
top--;
fa[a] = t;
}
return t;
}
int main()
{
freopen("paint.in","r",stdin);
freopen("paint.out","w",stdout);
scanf("%ld%ld%ld%ld",&n,&m,&p,&q);
long lm;long rm;
for (long i=1;i<n+1;i++)
{
fa[i] = i;
}
for (long i=m;i>0&&m-i+1<=n;i--)
{
long l = (i*p+q)%n +1;
long r = (i*q+p)%n +1;
if (l>r){long tmp=l;l=r;r=tmp;}
if(i==m){lm=(m*p+q)%n+1;rm=(m*q+p)%n+1;}
else if(l==lm&&r==rm)break;
for (long j=getroot(r);j>l-1;j=getroot(j))
{
if (c[j]==0)c[j] = i;
fa[j] = j-1;
}
}
for (long i=1;i<n+1;i++)
{
printf("%ld\n",c[i]);
}
return 0;
}