<状压DP>codevs 2451 互不侵犯

题面的链接
有点像八皇后,嗯,但是并不一样。
把每一行可以放的国王的情况压缩成二进制数,有国王用1表示,没有国王用0表示。
①预处理在一行之内合法的情况。也就是不能有相邻的1。
听说正解是用dfs跑的,我直接打了一个for循环。n最大只有9,状态数最多有2^9,不会超时。每种状态存在exist数组里
②预处理哪两种状态是可以挨着的,也就是上下两行可以同时存在的状态。同样记在一个数组里。上一个预处理时,可以把状态存到数组里,然后直接调用下标。存can数组
③同时,还要记录一下每一种状态的国王的数量,记为cnt数组。
④dp数组:dp[i][j][pos]表示放到第i行,已经放了j个国王,这一行的状态的下标为pos时的方案数。转移时,枚举上一行的状态,累加。即:dp[i][j][pos]=Σdp[i-1][j-cnt[pos]][k],其中,k为枚举的上一层的状态的下标
注意数据范围要开long long
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;
ll N,K,tot=1,max_exist,ans;
ll dp[15][100][1030],exist[1030],can[100][100],cnt[100];

void pre_exist()
{
    exist[1]=0;
    for(ll i=1;i<=max_exist;++i)
    {
        ll x=i,last=0,flg=0,num=0;
        while(x)
        {
            if(x&1) 
            {
                if(last==1) 
                {
                    flg=1;
                    break;
                }
                last=1;
                num++;
            }
            else last=0;
            x>>=1;
        }
        if(!flg) 
        {
            exist[++tot]=i;
            cnt[tot]=num;
        }
    }
    //for(ll i=1;i<=tot;++i) printf("*%lld,%lld\n",i,exist[i]);
}
void pre_com()
{
    for(ll i=1;i<=tot;++i) can[1][i]=can[i][1]=true;
    for(ll i=2;i<=tot;++i)
      for(ll j=i+1;j<=tot;++j)
        if((exist[i]&exist[j])==0&&(exist[i]&(exist[j]<<1))==0&&(exist[i]&(exist[j]>>1))==0) 
          can[i][j]=can[j][i]=true;
}
void done()
{
    for(ll i=1;i<=tot;++i) dp[1][cnt[i]][i]=1;
    for(ll i=2;i<=N;++i)
      for(ll j=0;j<=K;++j)
        for(ll pos=1;pos<=tot;++pos)
        {
            if(cnt[pos]>K) continue;
            for(ll k=1;k<=tot;++k)
            if(can[pos][k]&&cnt[k]+cnt[pos]<=j)//不要忘记判断放的王的数量是否超过当前枚举的j 
              dp[i][j][pos]+=dp[i-1][j-cnt[pos]][k];
        }
}
int main()
{
    scanf("%lld%lld",&N,&K);
    max_exist=(1<<N)-1;
    pre_exist();
    pre_com();
    done();
    for(ll i=1;i<=tot;++i)
      ans+=dp[N][K][i];
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值