题意
有n个男孩和m个女孩,排成一个n+m的序列,满足对于任意连续的一段,男孩与女孩的数目之差不超过a。求方案数。
n,m ≤ 150,k ≤ 20
题解
肯定朝递推的方向思考。如果序列前i个已经合法,现在多填入一个i+1,设男孩为1,女孩为-1,那么只需要考虑所有后缀和的绝对值是否小于等于a即可。
我们的关注点就在所有的后缀和上。每次填入一个数,所有后缀和都将+1/-1,且多生成了一个1/-1的后缀和。判断是否满足绝对值小于等于a,我们是不是只需要记下正数的最大值以及负数的绝对值的最大值就行了呢?
设k1为正数最大值,k2为负数绝对值最大值,当新来了一个1时,k1+1,k2-1,当新来了-1时,k1-1,k2+1。
状态的定义已经渐渐清晰了:f[i][j][k1][k2]表示已经填了i个1,j个-1,k1k2的含义同上,这样一个状态的方案数。
好像还是有点小问题:如果有后缀和在过程中符号变了会发生什么呢?因为所有后缀和都是同时增减的,大小关系不会变,所以被两个最值夹在中间的数肯定是没用的。而如果正数或负数都没有了也没关系,因为每次会多生成了一个1/-1,他会成为最值。
#include<cstdio>
#include<algorithm>
using namespace std;
const int MOD=12345678;
int n,m,K,ans,f[155][155][25][25];
int main(){
freopen("bzoj1037.in","r",stdin);
freopen("bzoj1037.out","w",stdout);
scanf("%d%d%d",&n,&m,&K);
if(K<1||!(n+m)){ printf("0\n"); return 0; }
if(!n){
if(m<=K) printf("1\n"); else printf("0\n");
return 0;
}
if(!m){
if(n<=K) printf("1\n"); else printf("0\n");
return 0;
}
f[0][0][0][0]=1; //n个1 m个-1
for(int j1=0;j1<=n;j1++)
for(int j2=0;j2<=m;j2++) //j1个 1 j2个 -1
for(int k1=0;k1<=K;k1++) //最大值 k1
for(int k2=0;k2<=K;k2++){ //最小值 -k2
if(!f[j1][j2][k1][k2]) continue;
int f_now=f[j1][j2][k1][k2];
if(j1<n&&k1<K) f[j1+1][j2][k1+1][max(0,k2-1)]+=f_now, f[j1+1][j2][k1+1][max(0,k2-1)]%=MOD;// +1
if(j2<m&&k2<K) f[j1][j2+1][max(0,k1-1)][k2+1]+=f_now, f[j1][j2+1][max(0,k1-1)][k2+1]%=MOD; // -1
}
for(int k1=0;k1<=K;k1++)
for(int k2=0;k2<=K;k2++)
ans=(ans+f[n][m][k1][k2])%MOD;
printf("%d\n",ans);
return 0;
}