【题目背景】
猪仙:馒头,馒头,又大又白的馒头······
话说猪仙最喜欢的食物就是馒头,但是他很不喜欢的颜色是白色,于是他买来了各种各样的果酱,给馒头们穿衣服······
【题目描述】
猪仙一共买来了N个馒头,他还买来了M种果酱,在用第i种果酱染色时,他会把第((i*p)+q) mod n +1 到 第((i*q)+p)mod n +1个馒头都涂成颜色i,每个馒头的颜色为它最后一次被染的颜色,没有被染过的馒头的颜色为0,现在猪仙想知道每个馒头最后的颜色,于是他找到了你,但你只有1s的时间解决这个问题因为他要花很长时间来吃馒头······
【输入文件】
连续的四个整数,分别表示M,N,P,Q。
【输出文件】
N行,第i行为第i个馒头的颜色。
【输入样例】
4 3 2 4
【输出样例】
3
2
2
0
【数据范围】
对于20%的数据 n<=1000 m<=10000
对于40%的数据 n<=10000 m<=100000
对于60%的数据 n<=50000 m<=500000
对于80%的数据 n<=300000 m<=3000000
对于100%的数据 n<=1000000 m<=10000000
所有数据中保证m*p+q 和 m*q+p 均小于maxlongint。
来源于NOI导刊
本题,模拟 2 个点
filldword(v+1,50,1) 可以过 3 个点(速度是 for 循环的 5 倍)。
线段树过六个点。
动态线段树加优化过八个点。
但以上这两种代码直奔两百行,这里主要讲能过全点的并查集。
首先,分析以上两种算法的不足之处:染色的复杂度,毫无疑问是 O(1) 的,时间主要浪费在了判断各个馒头是否染过色上。
而用并查集进行判断时,它的查找是跳跃性的,速度自然会快很多。
因为染色是每 n 个数一个循环,所以当 n 远大于 m 时,只需要求出每 n 个馒头的馒头颜色,循环输出即可,所以需要求得馒头的个数为 min(m,n)。
{$M 100000000}
program bread(input,output);
var
f:array[1..1000000] of longint;
x:array[1..1000000] of longint;
m,n,p,q,head,tail:longint;
procedure init;
begin
assign(input,'bread.in');reset(input);
readln(n,m,p,q);
close(input);
end;
function max(a,b:longint):longint;
begin
if a>b then
exit(a);
exit(b);
end;
function min(a,b:longint):longint;
begin
if a>b then
exit(b);
exit(a);
end;
function find(xx:longint):longint; //路径压缩
begin
if x[xx]=0 then
exit(xx);
f[xx]:=find(f[xx]);
exit(f[xx]);
end;
procedure main;
var
i:longint;
begin
fillchar(x,sizeof(x),0);
for i:=1 to n do
f[i]:=i+1; //初始化
for i:=m downto 1 do
begin
if i<=m-n then
exit;
head:=min((((i*p)+q)mod n)+1,(((i*q)+p)mod n)+1);
tail:=max((((i*p)+q)mod n)+1,(((i*q)+p)mod n)+1);
head:=find(head);
while head<=tail do
begin
x[head]:=i;
head:=find(head);
end; //每个馒头的颜色
end;
end;
procedure print;
var
i:longint;
begin
assign(output,'bread.out');rewrite(output);
for i:=1 to n do
writeln(x[i]);
close(output);
end;
begin
init;
main;
print;
end.