AtCoder Grand Contest 003 E - Sequential operations on Sequence 乱搞

题意

初始有一个长度为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/qyt[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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值