CF1575C 题解

First Post:

Last Update:

赛时想着用卷积处理散块,但不知道如何避免重复统计的问题,赛后看到题解做法大受震撼,故记录一下。有时一个观察能帮你省掉很多东西。

我们先考虑固定右端点 ,计算所有左端点的答案。

一个关键但显然的观察是, 的答案是相同的,这意味着我们只需计算 的答案,最后把答案乘 即可。

,即 序列的前缀和。如果 不大的话,我们大可以预处理出 表示满足 的个数。然后扫描 ,每次先让 ,再提取出 作为 的答案,再将 。容易发现,上述过程正确地统计了所有 的子段,可以手算验证。

我们发现,上述做法的瓶颈在于,如何快速地预处理

如果 ,处理起来比较容易。

否则,我们希望对于每个 ,我们能把 的贡献一起加入到 中,即需要对于 ,令 自增 。事实上,考虑一张图,对于每个点 ,连边 ,最后会形成一个长度为 的环(因为 是质数),那么我们的操作相当于对环上连续的一段进行区间加。维护差分数组即可做到

事实上,对于 不是质数的情况,上述做法也是可行的,只不过可能会形成若干个环,我们要对每个环分开考虑。

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,vr)
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;
// for(int i = 1;i <= n;i++) printf("%d ",sum[i]);printf("\n");
// for(int i = 0;i < k;i++) printf("%d ",Id[i]);printf("\n");
// for(int i = 1;i <= k;i++) if(dif[i]) printf("%d,%d ",i,dif[i]);printf("\n");
int ans = 0;
//(dif[Id[sall]] += 1) %= P;
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;
}