[agc003e]Sequential operations on Sequence

题目大意

有一个数字串S,初始长度为n,是1 2 3 4 …… n。
有m次操作,每次操作给你一个正整数a[i],你先把S无穷重复,然后把前a[i]截取出来成为新的S。
求m次操作后,每个数字在S中出现的次数。
n,m<=10^5,a[i]<=10^18。

解题思路

一般这种题考虑倒着退回去。
考虑一个函数solve(x,l),它返回的是一个cnt[1..n]表示每个数字在第x次操作后的序列的前l位中出现的次数。
发现solve(x,l)=(l/a[x-1])*solve(x-1,a[x-1])+solve(x-1,l%a[x-1])。
一种可行的做法是直接暴力算这个东西,递归到x=0的时候,就可以直接返回值。
考虑优化。首先可以发现,假如我们先不管前面那部分,可以发现l出现变化的次数不会超过log次。你至少每次减小一半。也就是说很多时候你后面那部分直接solve(x-1,l)。你只要快速地从x往前找到第一个a[x]<=l的即可。
再考虑前面那部分,这部分很显然,你不需要真正递归进去,只需要记录下每个solve(x,a[x])前面的系数,然后倒着扫即可。设系数数组xs[i]表示到目前为止,solve(i,a[i])的系数。xs[n]=1.
也就是说,一开始答案ans=xs[n]*solve(i,a[i]),然后你把它表示成log个xs[j]*solve(j,a[j])的和,然后你再把最大的那个solve继续表示成更小的solve相加。一直到最后,你会弄到x=0,利用xs[0]和并差分数组维护一下solve(0,y)(其中y!=n),就可以得出答案了。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=2e5+5,mo=1e9+7,rt=3;
ll Log[N],le,ri,l,r,i,j,n,m,mid,kan;
ll len,f[20][N],a[N],xs[N],inc[N],prt[N],sum;
void rmq()
{
    fo(i,0,m) f[0][i]=a[i];
    fo(i,1,m+1) Log[i]=trunc(log(i)/log(2));
    fo(j,1,Log[m+1])
        fo(i,0,m-(1<<j)+1)
            f[j][i]=min(f[j-1][i],f[j-1][i+(1<<j-1)]);
}
ll getmn(ll x,ll y)
{
    ll z=Log[y-x+1];
    return min(f[z][x],f[z][y-(1<<z)+1]);
}
ll find(ll v)
{
    le=0;
    ri=r;
    while (le<ri)
    {
        mid=(le+ri+1)/2;
        if (getmn(mid,r)<=v) le=mid;
        else ri=mid-1;
    }
    return le;
}
int main()
{
    freopen("t5.in","r",stdin);
    freopen("t5.out","w",stdout);
    scanf("%lld %lld",&n,&m);
    a[0]=n;
    fo(i,1,m)
        scanf("%lld",a+i);
    rmq();
    xs[m]=1;
    fd(i,m,1)
    {
        r=i-1;
        len=a[i];
        while (r>=0)
        {
            l=find(len);// pos<=r && a[pos]<=len
            xs[l]+=xs[i]*(len/a[l]);
            len%=a[l];
            r=l-1;
        }
        inc[len]+=xs[i];
    }
    fd(i,n,1)
    {
        sum+=inc[i];
        prt[i]=sum+xs[0];
    }
    fo(i,1,n) printf("%lld\n",prt[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值