【一句话题面】有一段长度为n的序列初始颜色均为0,给定两个参数p和q。有m次染色,第i次染色把(i* p+q)%n+1到(i* q+p)%n+1之间的格子染成颜色i。询问染色后各个点的颜色。
【分析】
此题在线线段树可AC。。。
然后我们来讲一下美妙的离线算法。
这不禁让我们想到了之前的一道模拟题。m次操作将序列中一段染成1。这里我们也可以用同样的方法。
确定染色顺序为从后往前,则所有染过的点都不需要再次染色,直接合并序列即可。
具体上,可以用并查集合并一段染过色,序号为这段区间的左端点。染色时从最右端开始染色,按集合跳跃右指针,每次把集合合并到最左端。最多合并n次区间,有m次询问所以总复杂度为O(n+m)。
因为各种原因,输出需要快写。。。。
Code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
//#define int long long
using namespace std;
const int maxn=1e6+1000;
int n,m,p,q;
int a[maxn];
int f[maxn];
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
inline void print(int x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
signed main(){
// freopen("bread.in","r",stdin);
// freopen("bread.out","w",stdout);
cin>>n>>m>>p>>q;
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++)
f[i]=i;
for(int c=m;c>=1;c--){
int x,y;
x=(c*p+q)%n+1,y=(c*q+p)%n+1;
if(x>y) swap(x,y);
if(find(x)==find(y)&&a[find(x)]!=0)continue;
while(x<=y){
y=find(y);
if(a[y]==0) a[y]=c,f[y]=find(x);
y--;
}
}
// for(int i=1;i<=n;i++){
// printf("%d\n",a[i]);
// }
for(int i=1;i<=n;i++){
print(a[i]);putchar('\n');
}
return 0;
}