[状压dp] P1896-SCOI2005-互不侵犯(小国王)

题意描述

N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

数据范围

1 <=N <=9, 0 <= K <= N * N

题目分析

类似蒙德里安的梦想的经典状态压缩dp。

1.状态表示

三维f[n][k][s],表示当前行为n、已放置了k个国王、且当前行放置国王的状态为s的方案数。
目标:

2.合法判断

由于要求国王互不攻击,故当前状态s必定有合法与不合法之分,且当前行的状态是否合法,仅与当前行的状态上一行的状态有关。
设当前行的状态为s,上一行的状态为s'

  • 对于当前行
    s的二进制表示中不能有两个连续的1
  • 对于当前行和上一行
    1.显然,ss'在任意相同位置的值不能均为1。即s&s'=0
    2.根据题意,ss'在任意相邻的位置不能均为1。即,s|s'的二进制表示不能有两个连续的1。
3.状态转移方程
  • 当前行为i、已放置了j个国王、且当前行放置国王的状态为k的所有方案数等于当前方案数+第i-1行已放置j-cnt[k]个国王,且状态为m的方案数。

  • 其中,cnt[k]为状态k中1的个数,m为满足k&m==0k|s的二进制表示没有两个连续的1的所有状态。

4.代码处理
  • 可以预处理出中所有合法(即当前行无两个连续的1)的状态值j,且记录下对应状态值的二进制表示中1的个数cnt[j]
5.代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=11,K=110,S=1<<11;
long long f[N][K][S],cnt[S];
bool st[S];    //判断预处理出的状态值是否合法
int n,k;
main()
{
    cin>>n>>k;
    for(int i=0;i<1<<n;i++) //预处理0到2^n-1的所有状态数
    {
        bool flag=1;
        int c=0;//记录i中1的个数
        for(int j=0;j<n;j++)
        {
            if(((i>>j)&1)&&(i>>(j+1)&1)) //若有两个相邻的1则非法
            {
                flag=0;break;
            }
            if((i>>j)&1) c++;
        }
        st[i]=flag?1:0;
        if(st[i]) cnt[i]=c;
    }

    for(int j=0;j<1<<n;j++)
        if(st[j]) f[1][cnt[j]][j]=1;//处理第一行的所有合法方案

    for(int i=2;i<=n;i++)   //从第二行开始处理
        for(int j=0;j<1<<n;j++)
            for(int m=0;m<1<<n;m++)  //枚举当前行的所有状态j与上一行的所有状态m
                if((j&m)==0&&st[j|m]) //如果j与m合法
                    for(int t=cnt[j];t<=k;t++)   
                        f[i][t][j]+=f[i-1][t-cnt[j]][m];    
    long long ans=0;
    for(int j=0;j<1<<n;j++) ans+=f[n][k][j];    //对所有合法方案求和
    cout<<ans;

第一篇题解,自学了markdown,从latex在线编辑器里拖了一堆公式引用进来,感觉不错。

posted @ 2022-03-17 19:12  Hssliu  阅读(48)  评论(0)    收藏  举报