bzoj 2054/2375. 疯狂的涂色
bzoj 2054/bzoj 2375
Description
给定一个长度为 \(n\) 的序列,共 \(m\) 次操作,再给定两个数 \(p\)、\(q\),每次把 \((i*p+q)\)%\(n+1\) 与 \((i*q+p)\)%\(n+1\) 之间的点染上颜色 \(i\) ,被染过色的会被新颜色覆盖,求最后每个点的颜色。
\(n,m≤10^7\)。
Solution
Solution 1 \(TLE\) code
我会暴力!
暴力从第1次操作到第m次往后覆盖,最后直接输出。
时间复杂度 \(O(mn)\),似乎一个点过不掉。
Solution 2 \(TLE\) code
考虑最简单的优化。
与铺地毯相同,这题也是只要求出最后的状态。
所以可以从后往前枚举,每次只对区间中没有染过色的点进行染色,染过的就不用管了,染到每个位置全部有色为止。
最坏时间复杂度:\(O(mn)\),可以水过一点数据。
轻易卡掉这个算法的数据:\(m\) 开到很大且其中有一个格子始终没有被涂上色。
Solution 3 \(AC\)
此题m巨大,肯定不能挨个处理一遍。考虑能不能在算法2的基础上再优化。
发现在算法2中,每次寻找没有染过色的店是直接暴力查找,这样很浪费时间。
设一个并查集,\(fa[i] = x\) 定义为指向第 \(i\)号格子后面下一个没有涂色格子的位置为 \(x\)。
仍然是倒序枚举,如果这次要把第 \(x\) 号格子给染了,那就指向后面一个位置的 \(fa\) 值(即指向 \(find(x + 1)\))。
最后就是如何判断是否全部染色完了。可以新增一个计数器,每染一个点就 \(+1\),加到 \(n\) 就退出。
时间复杂度:\(O(M)\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int M = 1E6 + 5;
int n, m, p, q;
int b[M];//b数组表示每个格子染色的状态
int fa[M];
int find(int x)
{
if(fa[x] == x)
{
return x;
}
else
{
return fa[x] = find(fa[x]);
}
}
int main()
{
cin >> n >> m >> p >> q;
int cnt = 0;
for(int i = 1; i <= n + 1; i++)
{
fa[i] = i;
}
for(int i = m; i >= 1; i--)
{
int l = (p * i + q) % n + 1;
int r = (q * i + p) % n + 1;//根据题意计算区间左右端点
if(l > r)
{
swap(l, r);
}
for(int j = find(l); j <= r; j = find(j))//只要在这个区间内就不停向后找未染色的点
{
b[j] = i;
fa[j] = j + 1;
cnt++;
}
if(cnt == n)//如果全部染好色了提前退出
{
break;
}
}
for(int i = 1; i <= n; i++)
{
printf("%d\n", b[i]);
}
}