SCOI2005 互不侵犯 [状压dp]

题目传送门

题目大意:有n*n个格子,你需要放置k个国王使得它们无法互相攻击,每个国王的攻击范围为上下左走,左上右上左下右下,共8个格子,求最多的方法数

看到题目,是不是一下子就想到了玉米田那道题,如果不会的话可以去我另外一篇博客里面看看,里面有玉米田详细解答方案.

好,回到这道题.首先,看到数据范围,很自然的想到状压dp.题目要求我们已经放了国王格子的上下左右以及左上右上左下右下都不能放国王,那么我们就可以通过上一行的状态来更新这一行的状态,即dp[i][state]表示到第i行状态为state满足条件的个数,但是这样很显然是不够的,因为题目只让我们选k个,所以还要加一维来储存个数,即dp[i][state][j]表示到第i行状态为state已经放置了j个国王的满足条件的个数

dp方程应该很好想吧,和玉米田差不多

dp[i+1][state][j]+=dp[i][state'][k]

现在来分步操作一下

首先是预处理,我们可以把左右两边均无1理解为一个状态没有相邻的1

void init()
{
    for(lol i=0;i<(1<<n);i++)
        if(!(i&(i<<1)))//如果这个状态没有相邻的1
        {
            can[++cnt]=i;//保存下来
            lol c=0;
            for(lol j=1;j<=n;j++) if(i&(1<<(j-1))) c++;//统计这个状态有多少个1
            sum[cnt]=c;
        }
}

 

然后就是初始化第1行

for(lol i=1;i<=cnt;i++)
{
    dp[1][can[i]][sum[i]]=1;
}

 

dp过程,题目要求左上右上均无1,那么我们可以把上一行的状态分别右移和左移,再相与,若为0则代表这个状态合法,还有就是转移状态的时候,sum[k]不能直接+=sum[j],然后dp[i+1][can[k]][sum[k]]+=dp[i][can[j]][sum[j]]因为要枚举很多次,这样sum[k]一直加下去会爆long long,所以改用for循环枚举

for(lol i=1;i<n;i++)//枚举第1~m-1行
    for(lol j=1;j<=cnt;j++)//枚举第i行的状态
        for(lol k=1;k<=cnt;k++)//枚举第i+1的状态
            if(!(can[j]&can[k]) && !(can[j]<<1&can[k]) && !(can[j]>>1&can[k]))//如果它对应的这一位以及左右都没有1
                for(lol l=0;l+sum[k]<=r;l++)//枚举这一个状态可以放多少个1
                    dp[i+1][can[k]][l+sum[k]]+=dp[i][can[j]][l];//转移状态

 

然后就是统计最后结果了,因为题目要求我们选k个,而且最终的结果都保存在最后一行,所以枚举最后一行的状态就行了

完整版代码,个人感觉还是比较简洁易懂的

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#define in(i) (i=read())
using namespace std;
typedef long long lol;
lol read()
{
    lol ans=0,f=1;
    char i=getchar();
    while(i<'0'||i>'9')
    {
        if(i=='-') f=-1;
        i=getchar();
    }
    while(i>='0'&&i<='9')
    {
        ans=(ans<<1)+(ans<<3)+i-'0';
        i=getchar();
    }
    return ans*f;
}
lol can[100],dp[10][1<<9][100],sum[100];
lol n,r,cnt,tot;
void init()
{
    for(lol i=0;i<(1<<n);i++)
        if(!(i&(i<<1)))//如果这个状态没有相邻的1
        {
            can[++cnt]=i;//保存下来
            lol c=0;
            for(lol j=1;j<=n;j++) if(i&(1<<(j-1))) c++;//统计这个状态有多少个1
            sum[cnt]=c;
        }
}
int main()
{
    lol ans=0;
    in(n);in(r);
    init();
    for(lol i=1;i<=cnt;i++)
        dp[1][can[i]][sum[i]]=1;
    for(lol i=1;i<n;i++)//枚举第1~m-1行
        for(lol j=1;j<=cnt;j++)//枚举第i行的状态
            for(lol k=1;k<=cnt;k++)//枚举第i+1的状态
                if(!(can[j]&can[k]) && !(can[j]<<1&can[k]) && !(can[j]>>1&can[k]))//如果它对应的这一位以及左右都没有1
                    for(lol l=0;l+sum[k]<=r;l++)//枚举这一个状态可以放多少个1
                        dp[i+1][can[k]][l+sum[k]]+=dp[i][can[j]][l];//转移状态
    for(lol i=1;i<=cnt;i++)
        ans+=dp[n][can[i]][r];//最后答案都在最后一行,记得开long long
    printf("%lld\n",ans);
    return 0;
}
View Code

 

posted @ 2018-03-20 20:04  real_l  阅读(402)  评论(0编辑  收藏  举报