题意
初始有一个长度为n的序列,第i个元素为i。现在有m个操作,第i个操作有一个参数
qi
q
i
,表示先把该序列当成循环结无限循环。然后取前q个元素作为新的序列。问m次操作后的序列中,1到n每个元素的出现次数。
n,m<=100000,q<=10^18
分析
首先注意到若
qi>qi+1
q
i
>
q
i
+
1
,那么
qi
q
i
是没用的,可以删掉。这样我们就把操作数组变为了一个单调递增序列。
设
t[i]
t
[
i
]
表示第i次操作完后的序列在最终序列中出现了多少次,其中
t[m]=1
t
[
m
]
=
1
。
考虑反过来操作,设当前处理到
t[x]
t
[
x
]
,注意到第x次操作完后的序列必然是由前面的序列循环后得到,那么可以把序列x拆成前面的序列。
维护一个k表示还有长度为k的后缀没有拆分。
注意到若
qy>k
q
y
>
k
,则对拆分没有帮助。
可以找到最大的y使得
qy<=k
q
y
<=
k
,那么有
t[y]+=k/qy∗t[x]
t
[
y
]
+
=
k
/
q
y
∗
t
[
x
]
,然后让
k=kmodqy
k
=
k
mod
q
y
即可。
最后剩下来的k必然满足
k<q1
k
<
q
1
,也就是说剩下的长度为k的后缀必然是
1,2,...,k
1
,
2
,
.
.
.
,
k
这样的一个序列,用差分前缀和统计一下即可。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=100005;
int n,m;
LL stack[N],t[N],ans[N],a[N];
int binary(int r,LL k)
{
int l=1;
while (l<=r)
{
int mid=(l+r)/2;
if (a[mid]<=k) l=mid+1;
else r=mid-1;
}
return l-1;
}
int main()
{
scanf("%d%d",&n,&m);
int top=0;
stack[++top]=n;
for (int i=1;i<=m;i++)
{
LL x;cin>>x;
while (x<=stack[top]) top--;
stack[++top]=x;
}
m=top;
for (int i=1;i<=top;i++) a[i]=stack[i];
t[m]=1;
for (int i=m;i>=1;i--)
{
LL k=a[i];int p=binary(i-1,k);
while (p)
{
t[p]+=k/a[p]*t[i];
k%=a[p];
p=binary(p-1,k);
}
ans[k]+=t[i];
}
for (int i=n;i>=1;i--) ans[i]+=ans[i+1];
for (int i=1;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}