【CF1750F Majority】(容斥+dp)
题意
定义一个 01 串 \(s\) 是好的,当且仅当 \(s\) 可以通过以下操作变成全是 \(1\) 的串,可以操作无数次。
选择 \(i,j\) 满足 \(i<j,s_i=s_j=1,2\sum_{k=i}^js_k\ge j-i+1\),然后将 \(k\in[i,j]\) 的 \(s_k\) 全部改为 \(1\)。
给定 \(n\) 和 \(m\),求有多少个长度为 \(n\) 的 \(01\) 串是好的,对 \(m\) 取模。
\(1 \leq n \leq 5000\)。
思路
注意到好的 01 串两端一定均为 \(1\)。考虑设 \(f_{i,j}\) 表示长度为 \(i\) 的 01 串操作到最终状态时,右端点所在的 \(1\) 连续段长度为 \(j\) 的方案数。
初始时,\(f_{1,1}=1\)。最终的答案就是 \(f_{n,n}\)。
对于 \(f_{i,i}\),考虑容斥,用总的方案数减去所有的不合法方案数,即:
\[f_{i,i}=2^{i-2}-\sum_{j=1}^{\left \lfloor \frac{i-1}{2} \right \rfloor } f_{i,j}
\]
而对于其他状态,中间一定存在一个较长的 \(0\) 连续段,于是可以枚举中间 \(0\) 连续段的长度和右侧 \(1\) 连续段的长度,需要注意边界条件,这里右侧的 \(1\) 连续段不能和左侧的进行操作,即:
\[f_{i,j}=f_{j,j}\sum_{k=j+2}^{i-j-1} \sum_{l=1}^{k-j-1} f_{i-j-k,l}
\]
其中的两个限制条件也就是 \(j+k<i,j+l<k\),稍微改写一下可以得到 \(l+(i-j-k)<i-2j\)。那么只需要维护一个 \(s_i\) 表示所有 \(j+k \leq i\) 的 \(f_{j,k}\) 之和即可做到 \(O(n^2)\) 转移。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5010;
int n,m,f[N][N],sum1[N<<1],sum2[N<<1];
void Add(int &a,int b){a+=b;if(a>=m) a-=m;}
void Sub(int &a,int b){a-=b;if(a<0) a+=m;}
int main()
{
scanf("%d%d",&n,&m);f[1][1]=1;int mul_2=1;sum1[2]=1;
for(int i=2;i<=n;i++)
{
f[i][i]=mul_2;mul_2=mul_2*2%m;
for(int j=1;j<=n;j++) sum2[j]=(sum2[j-1]+sum1[j])%m;
for(int j=1;j*2<i;j++)
{
Add(f[i][j],1ll*f[j][j]*sum2[i-2*j-1]%m);
Sub(f[i][i],f[i][j]);
}
for(int j=1;j<=i;j++) Add(sum1[i+j],f[i][j]);
}
printf("%d\n",f[n][n]);
return 0;
}

浙公网安备 33010602011771号