【题解】Atcoder AGC#03 E-Sequential operations on Sequence

  仙题膜拜系列...首先我们可以发现:如果在截取了一段大的区间之后再截取一段小的区间,显然是没有什么用的。所以我们可以将操作序列变成单调递增的序列。

  然后怎么考虑呢?启示:不一定要考虑每一个数字出现的次数——我们还可以计算每一段完整的序列出现的次数。如果我们求出第 \(i\) 次操作过后产生的序列在答案中共出现了 \(rec[i]\) 次,那么第 \(i - 1\) 次操作过后产生的序列必然在答案中出现了 \(\frac{len[i]}{len[i - 1]} * rec[i]\) 次。可是这样还会剩下\(rec[i]\) 段 \(len[i] \ mod \ len[i - 1]\) 的小区间。我们可以考虑如何统计这一段小区间中出现了哪些完整的序列。

  由于要统计在这段小区间中出现了哪些完整的序列,我们可以找到操作序列中第一个长度小于它的位置\(pos\)。那么此时这段小区间中会有 \(\frac{nowlen}{len[pos]}\) 段长度为 \(len[pos]\) 的完整区间。显然这是一个递归求解的问题,而一个数最多取模 log 次的性质保证我们最多会递归 log 次。如果最后剩下的区间比第一个小区间还要小,那么我们可以直接知道对于那些数字的出现做出了贡献,维护差分序列区间加一下就好了。

  **启示:可以转化一下思路,不一定要计算每个数字的贡献,而可以从后往前递推出每段序列出现的次数,将一段序列看作一个整体。而一个数取模最多不会超过 log 次的限制也可以保证递归的次数。

#include <bits/stdc++.h>
using namespace std;
#define maxn 1000000
#define int long long
int n, Q, m;
int rec[maxn], len[maxn], ans[maxn]; 

int read()
{
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

void Solve(int x, int t)
{
    if(!x) return;
    int pos = upper_bound(len + 1, len + 1 + m, x) - len - 1;
    if(!pos) { ans[1] += t; ans[x + 1] -= t; }
    else
    {
        rec[pos] += (x / len[pos]) * t; 
        Solve(x % len[pos], t);
    }
}

signed main()
{
    n = read(), Q = read(); len[++ m] = n;
    for(int i = 1; i <= Q; i ++)
    {
        int x = read();
        while(m && len[m] >= x) m --;
        len[++ m] = x;
    }
    rec[m] = 1;
    for(int i = m; i > 1; i --)
    {
        rec[i - 1] += rec[i] * (len[i] / len[i - 1]);
        Solve(len[i] % len[i - 1], rec[i]);
    }
    ans[1] += rec[1], ans[len[1] + 1] -= rec[1];
    for(int i = 1; i <= n; i ++)
    {
        ans[i] += ans[i - 1];
        printf("%lld ", ans[i]);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/twilight-sx/p/9823033.html