【做题】agc003E - Sequential operations on Sequence——经典结论
题意:有一个序列,初始是从\(1\)到\(n\)的\(n\)个数。有\(q\)次操作,每次操作给出\(q_i\),把当前的序列重复无数遍,然后截取最前面的\(q_i\)个元素作为新序列。要求输出完成所有\(q\)次操作后,每个\(1\)到\(n\)的数各出现了多少次。
\(n,q \leq 10^5, \, q_i \leq 10^{18}\)
首先一个要点在于操作后序列的长度可以达到\(10^{18}\)。因此,要从每次操作间寻找思路。
首先,我们发现如果一次操作之后又\(q_i\)比它小的操作,那么它就是没有意义的。所以,我们可以让\(q_i\)单调递增。然后,考虑每一次操作后的结果,它就是前一次操作后的结果重复若干遍,再加上前一次操作的一个前缀。前面一部分可以通过计算前一次操作后的结果时乘上一个系数来处理。那么,问题就变成如何后面的不完整部分了。
更确切地说,我们的答案还要加上一次操作后结果的一个前缀,其长度是\(q_i \bmod p_{i-1}\)。这里敏感的人或许已经发现了,我们又一个经典的结论,就是一个数对一个不大于它的数取模后,至少减小一倍。证明显然。那么,我们每次二分小一个不大于它的\(q_i\),最多做\(O(\log n)\)次后就能把这个长度缩小到\(n\)以内。到最后,就是答案序列的一个前缀加了。这里的复杂度是\(O(\log^2 n)\)。
因此,我们就能在\(O(n \log^2 n)\)时间内解决本题。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100010;
int q,n,len[N],val[N],ans[N],m;
void doit(int x,int v) {
if (!x) return;
int t = upper_bound(len+1,len+m+1,x) - len - 1;
if (t == 0) ans[1] += v, ans[x+1] -= v;
else {
val[t] += (x / len[t]) * v;
doit(x % len[t],v);
}
}
signed main() {
int x;
scanf("%lld%lld",&n,&q);
len[++m] = n;
for (int i = 1 ; i <= q ; ++ i) {
scanf("%lld",&x);
while (m && len[m] >= x) -- m;
len[++m] = x;
}
val[m] = 1;
for (int i = m ; i >= 2 ; -- i) {
val[i-1] += val[i] * (len[i] / len[i-1]);
doit(len[i] % len[i-1],val[i]);
}
ans[1] += val[1];
ans[len[1]+1] -= val[1];
for (int i = 2 ; i <= n ; ++ i)
ans[i] += ans[i-1];
for (int i = 1 ; i <= n ; ++ i)
printf("%lld\n",ans[i]);
return 0;
}
小结:还是不够熟练。以及,想题时用笔整理思路,或许是个好习惯。