AGC003E
最后还是看了题解。
首先用单调栈把操作序列变成单调递增的(证明去掉一个相邻逆序对中的大数不会对答案产生影响)。
后面的内容想出来是几天之后了……
我描述不太清楚, 这里仅给出大体思路:
题目的目标就是要维护一个答案数组 \(\tt ans[i]\), 其中 \(\tt ans[i]\) 表示所有操作操作完之后 \(\tt i\) 的数量 。
为了表达清楚那么一点点,现在在这里定义两种与序列相关的运算(设有一数列 \(\tt q\)):
- 数乘, \(\tt k*q, \ k\in\Bbb N_+\) ,表示将 \(\tt q\) 复制 \(\tt k\) 次拼接起来
- 答案数组加上数列 \(\tt q\), \(\tt ans += q\), 表示对于 \(\tt \forall i\in[1,|q|]\), 让 \(\tt ans[q_i]\) 加上 \(\tt 1\)
设操作总数为 \(\tt Q\), 设第 \(\tt i\) 次操作后的数列为 \(\tt S_i\)。
那么题目要求的就是要进行 \(\tt ans += S_Q\) 之后输出整个 \(\tt ans\) 数组。
显然, \(\tt S_i\) 可以被分解为若干段 \(\tt S_{i-1}\) 和一段 \(\tt S_i\mod S_{i-1}\) (描述不精确, 感性理解下)
那么 \(\tt ans += S_i\) 就相当于先后进行 \(\tt ans += \lfloor\frac{|S_i|}{|S_{i-1}|} \rfloor*S_{i-1}\), \(\tt ans += S_i\mod S_{i-1}\) 。
注意到这个 \(\tt ans += S_i\mod S_{i-1}\) 的操作也是可以像刚才那样分解, 套一个二分, 分解完只需要 \(\tt O(log^2)\) 的时间。
整个算法的总体思路就是把 “给答案数组加上一个较晚的数列” 不断拆成 “给答案数组加上若干个不一定相同的较早的数列”。
//A了, 函数内参数没开longlong
#include<bits/stdc++.h>
using namespace std;
const int MN = 100003;
int n,q,tp;
long long a[MN];
long long times[MN], ans[MN];
void divide(long long len, long long timeS) { // 把给 “ans数组加上第 id 次操作后的序列的长度为 len 的前缀 timeS 次” 分解
int j = upper_bound(a+1,a+tp+1,len) - a - 1;
if(!j) ans[1]+=timeS, ans[len+1]-=timeS;
else {
times[j] += (len/a[j]) * timeS;
divide(len%a[j], timeS);
}
}
int main() {
scanf("%d%d", &n,&q);
a[++tp] = (long long)n;
for(int i=1;i<=q;++i) {
long long x;
scanf("%lld",&x);
while(tp && a[tp]>=x) --tp;
a[++tp] = x;
}
times[tp] = 1ll; //题目就是要求把 ans 数组加上一个最终数列
for(int i=tp;i>1;--i) {
times[i-1] += (a[i]/a[i-1])*times[i]; //分解前面的整段
divide(a[i]%a[i-1],times[i]);
}
ans[1] += times[1];
ans[a[1]+1] -= times[1];
for(int i=2;i<=n;++i) ans[i] += ans[i-1];
for(int i=1;i<=n;++i) cout << ans[i] << '\n';
return 0;
}