SGU 223 Little Kings

状态压缩DP

题意:输入n,m,一个n*n的棋盘,放入m个国王,国王不能相互攻击,有多少种放置的可能。国际象棋的国王的攻击范围就是它周围的一圈,一共8个格子(上下左右四个对角)

poj 1185 炮兵布阵 非常相似 , 同样使用位运算加速,同样可以使用滚动数组

代码有详细注释

 

/*
state[i]表示第i种状态,即一行内的国王不互相攻击的状态,king[i]对应这种状态有多少个国王

关于相邻两行的状态是否能共存的问题,也就是两行的国王会不会相互攻击,state[k]表示第i行的状态,state[kk]表示第i-1行的状态
state[k] & state[kk] = 1  , 说明在某一列上有国王直接对应着,攻击了
state[k] & (state[kk]<<1) = 1 , 先将i-1行的状态左移一位,比较为1,说明相邻的两列有国王,那也是会攻击的
初写的时候漏了这个,后来很快发现了 (state[k]<<1) & state[kk] = 1 ,这同样说明相邻两列有国王互相攻击

按行DP,当前这行的信息只由上面那行决定,所以DP的状态定义也只和上层有关
dp[i][j][k],表示到第i层为止,一共放了j个国王,且第i层的状态为state[k]
所以我们要求的目标状态就是  sum{ dp[n][m][k] } 即全部n层一共放入了m个国王,其中第n层可能的状态为state[k]
状态转移方程
dp[i][j][k]=sum{ dp[i-1][mm][kk]} 即前i-1层放了mm个国王,第i-1层的状态为state[kk],
第i层状态为state[i],对应的国王数加上mm刚好是j。
*/
#include <cstdio>
#include <cstring>
#define N 15
#define M 110
#define MAX 550

int state[MAX];  //记录一行以内可能的状态
int king[MAX];   //对应每个状态放了多少个国王
int nums;        //状态总数
long long dp[N][M][MAX];   //dp[i][j][k],到第i行一共放了j个国王,第i行的状态为state[k]的方案数
int n,m;

void DP()
{
    int i,j,k,c,mm,kk,cc;
    long long ans;
    memset(dp,0,sizeof(dp));
        
    //先初始化第1行的情况
    for(i=0; i<nums; i++) //枚举一行可能出现的状态
    {
        j=king[i];  //在这个状态下放了多少个国王
        if(j<=m) dp[1][j][i]++; //方案数计数,该行放的个数不能超过总数
    }

    for(i=2; i<n; i++) //从第2行dp到第n-1行
    {
        for(j=0; j<=m; j++)  //到第i行,一共放了j个国王
        {
            for(k=0; k<nums; k++) //当前第i行的状态
            {
                c=king[k]; //第i行放了多少个国王
                if(c>j) continue; //若当前行的国王数就超过总数j,那么跳过
                mm=j-c;  //那么前i-1行的总数是mm
                for(kk=0; kk<nums; kk++) //枚举第i-1行的状态
                {
                    cc=king[kk];  //第i-1行放了多少个国王
                    if(cc>mm) continue; //若当前行的国王数就超过总数mm,那么跳过
                    if(state[k] & state[kk]) continue;
                    //第i行和第i-1行的国王有攻击
                    if(state[k] & (state[kk]<<1)) continue;
                    //第i行和第i-1行的国王有攻击
                    if(state[kk] & state[k]<<1)   continue;
                    //第i行和第i-1行的国王有攻击
                    dp[i][j][k] += dp[i-1][mm][kk];
                }
            }
        }
    }
    
    ans=0;
    for(k=0; k<nums; k++) //枚举最后一行的状态
    {
        c=king[k];  //该行的国王数
        if(c>m) continue; //该行的国王数就超过总数,跳过
        j=m-c;  //前n-1行的总数
        for(kk=0; kk<nums; kk++)  //第n-1行的状态
        {
            cc=king[kk];  //第n-1行的状态
            if(cc>j) continue; //第n-1行的国王数超过了总数那么跳过
            if(state[k] & state[kk]) continue;
            //第i行和第i-1行的国王有攻击
            if(state[k] & (state[kk]<<1)) continue;
            //第i行和第i-1行的国王有攻击
            if(state[kk] & state[k]<<1)   continue;
            
            dp[n][m][k] += dp[n-1][j][kk];
        }
        ans += dp[n][m][k];
    }

    printf("%lld\n",ans);
}

void init_state()
{
    nums=0;
    for(int i=0; i<(1<<n); i++) //所有可能的状态
    {
        if( i & (i<<1) ) continue; //行内互相攻击丢弃
        int t=i;
        king[nums]=0;
        while(t)  //统计在i状态下放了多少个国王
        { king[nums] += (t&1); t=t>>1; }
        state[nums++]=i;  //保存这个合法的状态
    }
}

int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init_state();  //找出所有可能的状态
        DP();  //动态规划解决
    }
    return 0;
}

 

 

最近换了高亮和背景的配色,看着舒服多了

posted @ 2013-03-07 23:03  Titanium  阅读(325)  评论(0编辑  收藏  举报