题意:
有一个长度为n的序列,初始为1~n。m个操作,每个操作ai表示把当前序列复制无限次,然后取前ai个数作为新序列。问最终序列里1~n各出现多少次。
n,m<=10^5
ai<=10^18
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#define N 110000
#define LL long long
using namespace std;
LL sta[N],a[N],t[N],s[N],ans;
int tp,n,m;
int td(LL k,int r)
{
int l=1,res=0;
while(l<=r)
{
int mid=(l+r)/2;
if(a[mid]<=k) res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
sta[tp=1]=n;
for(int i=1;i<=m;i++)
{
LL x;scanf("%lld",&x);
while(x<=sta[tp]) tp--;
sta[++tp]=x;
}
m=tp;
for(int i=1;i<=m;i++) a[i]=sta[i];
t[m]=1;
for(int i=m;i>=1;i--)
{
LL k=a[i];int p=td(k,i-1);
while(p)
{
t[p]+=(k/a[p])*t[i];
k%=a[p];
p=td(k,p-1);
}
s[1]+=t[i];s[k+1]-=t[i];
}
for(int i=1;i<=n;i++) {ans+=s[i];printf("%lld\n",ans);}
return 0;
}
题解:
感觉做法很不显然,真不知道出题人怎么想的。。
最开始先取一次n,方便处理。
发现如果
ai>=ai+1
那么ai是无效的,把序列变成严格上升的。
然后令t[i]为第i次操作后的序列在最终序列中出现多少次,有t[m]=1。
i从大到小枚举
然后考虑对于t[i],把他等价的转移到某些t[j]上(i>j)
维护一个k表示当前还有多长的后缀没有转移,由于每次转移拿掉的都是周期,所以这个k是个border
无视就好,和解法没有关系
循环这个过程:
1、找到最大的j,使
aj<=k
,那么当前的后缀k就是第j个序列循环得到的
2、
t[j]+=kaj∗t[i]
,然后k对
aj
取模
最后剩下的k是小于
a1
同时也小于n的,就是代表最初的k个数,直接处理答案就好。
用心感受一下,就能体会到正确性了。。
k每次取模至少会变小一半,用二分找j的位置就是
O(mlog(m)log(am))
的了。。