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
", b[i]);
}
}