1. 程式人生 > >【題解】Atcoder AGC#03 E-Sequential operations on Sequence

【題解】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; }