[ARC117E] Zero-Sum Ranges 2 题解
DengDuck
·
·
题解
upd:更正笔误。
不会简单计数题,加训!
首先普遍的想法是考虑对于一个已知序列怎么快速统计答案。
求前缀和数组 s,设 b_i 表示存在多少 x 满足 s_x=i。
则答案为:
\sum_{i=-n}^n\binom{b_i}{2}
注意到每一种前缀和是独立的,数字是 +1 和 -1,所以前缀和每次要么上一层,要么下一层,变成这个样子:
我们考虑分层做动态规划,从下到上还是从上到下?从上到下。
因为最后合法的数列满足左右两边的值都是 0,为了满足这一点,对于合法的数列,每次加入一层的时候,两边都一定是新加的层的那个值。
你发现,从上到下加层,你就可以比较容易地满足这一点,是的,这让人很爽,动态规划比较自然,然后你就舒服了。
从上向下加层的图可以参考以下这张:
然后我们思考加上下一层怎么加,注意到上一次这些相邻的值,还有两边的间隙了吗?这就是我们放东西的地方。
所以这个时候就可以比较自然地想到状态了,设 f_{i,j,k} 表示目前加的层里我一共放了 i 个点,产生了j 个区间和为 0 个区间,有 k 个间隙(两边的咱们不算,反正你知道那个东西存在,特殊的东西特殊处理)。
初始化就是枚举第一层有几个点就行了。
考虑转移,枚举这一层放 x 个点:
f_{i,j,k}\to \binom{x-1}{k+1}f_{i+x,j+\binom{x}{2},x-(k+2)}
其中 \binom{x-1}{k+1} 是考虑怎么把这 x-1 个点放进间隙中,有这么多方案数,这是一个插板法,你有 k+1 块板子,因为你有 k+2 个间隙,相当于 k+2 个盒子。
然后你有 x 个球,所以有 x-1 个可以插的间隙,这就是一个经典的组合数学问题。
然后直接求就行了,放个图大概更好理解:
最后你发现,前缀和可以是负数啊!可是你这个状态 0 就是最后一层了。
怎么办?做法假了?
没有关系,大力枚举所有状态,然后对于最后一层的间隙,直接和另外一种方案合并。
> 有人问这里为什么和是 $2n+1$,因为前缀和数组的长度要算上 $s_0=0$ 啊。
所有状态都这么统计就行了,时间复杂度为 $\mathcal O(n^3m)$,可以过。
所有图片来自官方题解。
```cpp
#include<bits/stdc++.h>
#define LL long long
#define pb push_back
using namespace std;
const LL N=105;
const LL M=1805;
LL n,m,f[N][M][N],ans,c[N][N];
LL C(LL n,LL m)
{
if(m>n||m<0||n<0)return 0;
return c[n][m];
}
int main()
{
cin>>n>>m;
c[0][0]=1;
for(int i=1;i<=2*n;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
}
for(int i=1;i<=n+1&&C(i,2)<=m;i++)
{
f[i][C(i,2)][i-1]=1;
}
for(int i=1;i<=2*n+1;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=n;k++)
{
for(int x=k+2;i+x<=2*n+1&&x<=n+1;x++)
{
if(j+C(x,2)>m)continue;
f[i+x][j+C(x,2)][x-k-2]+=f[i][j][k]*C(x-1,k+1);
}
}
}
}
ans=f[2*n+1][m][0];
for(int i=0;i<=2*n+1;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=1;k<=n;k++)
{
ans+=f[i][j][k]*f[2*n+1-i][m-j][k-1];
}
}
}
cout<<ans<<endl;
return 0;
}
//RP++
```