[递推] BZOJ1037: [ZJOI2008]生日聚会Party

题意

有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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值