赛时想着用卷积处理散块,但不知道如何避免重复统计的问题,赛后看到题解做法大受震撼,故记录一下。有时一个观察能帮你省掉很多东西。
我们先考虑固定右端点
,计算所有左端点的答案。
一个关键但显然的观察是, 和
的答案是相同的,这意味着我们只需计算 的答案,最后把答案乘 即可。
设
,即 序列的前缀和。如果 不大的话,我们大可以预处理出 表示满足 的 的个数。然后扫描 ,每次先让 减 ,再提取出 作为 的答案,再将 加 。容易发现,上述过程正确地统计了所有
和 的子段,可以手算验证。
我们发现,上述做法的瓶颈在于,如何快速地预处理 。
如果 ,处理起来比较容易。
否则,我们希望对于每个 ,我们能把 的贡献一起加入到 中,即需要对于 ,令
自增 。事实上,考虑一张图,对于每个点 ,连边 ,最后会形成一个长度为 的环(因为
是质数),那么我们的操作相当于对环上连续的一段进行区间加。维护差分数组即可做到
。
事实上,对于
不是质数的情况,上述做法也是可行的,只不过可能会形成若干个环,我们要对每个环分开考虑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <bits/stdc++.h> using namespace std; const int N = 2e5 + 5,P = 1e9 + 7; int n,m,k,sum[N]; int cnt[N]; int Id[N]; int dif[N]; int main() { cin >> n >> m >> k; for(int i = 1;i <= n;i++) cin >> sum[i],(sum[i] += sum[i - 1]) %= k; int sall = 1ll * sum[n] * m % k; int sn = sum[n]; if(!sn) { int res = 0; for(int i = 1;i <= n;i++) (res += cnt[sum[i]]++) %= P; int ans = (1ll * ((res << 1ll) + n) % P * m % P * m % P - (1ll * n * m % P) + P + 1) % P; cout << ans << endl; return 0; } Id[0] = 1; for(int i = sn,j = 2;i;i = (i + sn) % k,++j) Id[i] = j; for(int i = 1;i <= n;i++) { int vl = sum[i],vr = (sum[i] + 1ll * m * sn % k) % k; vl = Id[vl];vr = Id[vr]; (dif[1] += m / k) %= P; if(vl <= vr) dif[vl]++,dif[vr]--; else dif[vl]++,dif[1]++,dif[vr]--; } for(int i = 1;i <= k;i++) (dif[i] += dif[i - 1]) %= P; int ans = 0; for(int r = 1;r <= n;r++) { (dif[Id[sum[r]]] += P - 1) %= P; (ans += dif[Id[(sum[r] + sall) % k]]) %= P; (dif[Id[(sum[r] + sall) % k]] += 1) %= P; } ans = 1ll * ans * m % P; if(!sall) ans = (ans + 1) % P; cout << ans << endl; return 0; }
|