疯狂的馒头
疯狂的馒头
CQF 十分喜欢吃馒头。
兴奋之下他一下子买了 $N$ 个馒头请所有认识他的人吃。
但是 CQF 不喜欢白色,喜欢红色、黄色、绿色等鲜艳的颜色。
于是他把所有白色的馒头排成一列。
然后进行 $M$ 次染色操作。
每个染色操作都是用一个神奇的刷子把连续的多个馒头染成特定的某种颜色。
一个馒头最终的颜色是最后一次染它的颜色。
如果一个馒头没有被染过色,那么它的颜色就是白色。
现在 CQF 已经定好了染色计划:在第 $i$ 次染色操作中,把第 $(i \times p+q) \bmod{N}+1$ 个馒头和第 $(i \times q+p) \bmod{N}+1$ 个馒头之间的馒头染成颜色 $i$,其中 $p,q$ 是特定的两个正整数。
他想立即知道最后每个馒头的颜色。
你能帮他吗?
输入格式
第一行四个正整数 $N,M,p,q$。
输出格式
一共输出 $N$ 行,第 $i$ 行表示第 $i$个馒头的最终颜色(如果最终颜色是白色就输出 $0$)。
数据范围
在 $20\%$ 的数据中,$1 \leq N \leq {10}^3,1 \leq M \leq {10}^4$
在 $40\%$ 的数据中,$1 \leq N \leq {10}^4,1 \leq M \leq {10}^5$
在 $60\%$ 的数据中,$1 \leq N \leq 5 \times {10}^4,1 \leq M \leq 5 \times {10}^5$
在 $80\%$ 的数据中,$1 \leq N \leq 3 \times {10}^5,1 \leq M \leq 3 \times {10}^6$
在 $100\%$ 的数据中,$1 \leq N \leq {10}^6,1 \leq M \leq {10}^7$
保证所有输入数据中 $1 \leq M \times p+q,M \times q+p \leq 2^{31}−1$。
输入样例:
4 3 2 4
输出样例:
2 2 3 0
解题思路
直接正着去做的话需要用到线段树或者splay,但时间复杂度是$O(m \log{n})$,而且常数比较大因此大概率会超时。
如果正着枚举,那么对于任意阶段每个馒头的颜色都是不确定的,因为在下一阶段可能又被染成其他的颜色。而如果反过来去做,对于还没染色的馒头那么当前要染的颜色就是这个馒头最终的颜色,对于已染色的馒头那么已染的颜色就是最终颜色,而不应该变成当前要染的颜色。可以发现,反着做就等价于每次把区间的某些位置删掉(即把未染色的馒头染色再删掉,下次就不会再染到这个馒头了),而每次操作都是这种删除操作,那么可以继续转换成维护区间连通性的问题,即维护删除的位置的连通性,每次删除一个位置,那么就把左右两边已删除的位置所对应的连通块进行合并,这个可以用并查集来实现。
并查集中每一个集合的代表元素都指向自己,表示集合中除了代表元素外,每一个元素往右边找第一个还没被合并的位置(即代表元素)。当把某个位置$x$合并后,那么就把$x$指向下一个位置$x+1$所在集合的代表元素,即 fa[x] = find(x+1),其中$x$一定是集合的代表元素,因此不需要 find(x)。这样做就可以维护区间的连通性,无论是每次是往区间中加入或者是删除某些位置。
初始的时候每个元素都是独立的,因此都指向自己。并查集的大小要开到$n+1$,因为每次都需要与下一个位置合并,因此当$x=n$,那么需要用到$n+1$这个位置。
AC代码如下,时间复杂度可以认为是$O(m+n)$:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e6 + 10; 5 6 int fa[N], w[N]; 7 8 int find(int x) { 9 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 10 } 11 12 int main() { 13 int n, m, p, q; 14 scanf("%d %d %d %d", &n, &m, &p, &q); 15 for (int i = 1; i <= n + 1; i++) { // 并查集要多开一个位置 16 fa[i] = i; 17 } 18 for (int i = m; i; i--) { 19 int l = (i * p + q) % n + 1; 20 int r = (i * q + p) % n + 1; 21 if (l > r) swap(l, r); // 把区间[l,r]中未染色的馒头染色,即把区间中没有合并的位置进行染色 22 l = find(l); // 找到l右边第一个没被合并的位置 23 while (l <= r) { 24 w[l] = i; // 染色 25 fa[l] = find(l + 1); // 当前位置被染色,与右边位置所在集合进行合并 26 l = find(l); // 继续找到l右边第一个没被合并的位置 27 } 28 } 29 for (int i = 1; i <= n; i++) { 30 printf("%d\n", w[i]); 31 } 32 33 return 0; 34 }
参考资料
AcWing 3115. 疯狂的馒头【并查集区间染色模型 / 线段树剪枝】:https://www.acwing.com/solution/content/60830/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17320578.html