P10036 题解

一句话题意:从长度为 $n$ 的数组 $a$ 中取出三个不相同的区间,问有多少种不同的取法使区间和之和为零(无序)。

根据题意,$b_x+b_y+b_z=0$,假设 $b_x$ 对应的区间为 $a_{i’+1} \cdots a_i$,$b_y$ 对应 $a_{j’+1} \cdots a_j$,$b_z$ 对应 $a_{k’+1} \cdots a_k$。我们可以使用前缀和优化这个式子:$s_i-s_{i’}+s_j-s_{j’}+s_k-s_{k’}=0$,其中 $s_x=\sum_{i=1}^x a_i$。

但是我们发现枚举式子右侧的六个变量时间复杂度为 $\mathcal{O}(n^6)$,无法接受,那么我们可以把三项移到右侧,将式子变为:$s_i-s_{i’}+s_j=s_{j’}-s_k+s_{k’}$。这样对两边的枚举时间复杂度均降为 $\mathcal{O}(n^3)$,可以分开枚举(注意实现时要保证 $j’<j$,对此我们可以在计算 $j$ 前更新一遍 $j’$,这样可以在不破坏效率的情况下维护 $j’<j$,具体可见代码)

但我们发现以上算法有两个问题:

  1. 无法保证 $x<y<z$,换句话说就是会重复计算。

  2. 可能会统计到不合法的答案(如两个相同的区间)

我们重新观察式子,发现:

  1. 当 $x,y,z$ 互不相等时,共会计算 $3!=6$ 次。

  2. 当 $x,y,z$ 中有且仅有两数相等时(不合法),会计算 $2$ 次。

  3. 当 $x=y=z$(不合法)时,会计算 $1$ 次。

于是我们可以再 $\mathcal{O}(n^2)$ 枚举相等的数,减掉不合法的情况;最终答案即为剩下的合法计数的 $\frac16$。如果对实现或思路有问题,可参考代码(作者太弱了降不太清楚 QAQ)。

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
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=505,Mx=3e7+5;
int n,a[N],sum[N],f[Mx],base;
map<int,int> g;

inline int id(int x) {return max(x-base+1,0);}

int main() {
scanf("%d",&n);
int tmp=0;
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
base=max(base,sum[i]-tmp),tmp=min(tmp,sum[i]);
for(int i_=0;i_<i;i_++) g[sum[i]-sum[i_]]++;
}
base=tmp-base;
// s[i]-s[i']+s[j]=s[j']-s[k]+s[k']
ll ans=0;
for(int j=1;j<=n;j++) {
int j_=j-1;
for(int k=1;k<=n;k++)
for(int k_=0;k_<k;k_++)
f[id(sum[j_]-sum[k]+sum[k_])]++;
for(int i=1;i<=n;i++)
for(int i_=0;i_<i;i_++)
ans+=(ll)f[id(sum[i]-sum[i_]+sum[j])];
}
for(int i=1;i<=n;i++)
for(int i_=0;i_<i;i_++) {
int s=sum[i]-sum[i_];
ans-=3LL*g[-2*s];
if(!s) ans+=2LL;
}
printf("%lld\n",ans/6LL);
return 0;
}