【SCOI2005】互不侵犯 题解(状压DP)

前言:一道状压DP的入门题(可惜我是个DP蒟蒻QAQ)

------------------

 

题意简述:求在一个$n*n$的棋盘中放$k$个国王的方案数。注:当在一个格子中放入国王后,以此格为中心的九宫格的其他八个格子将不能放置国王。

数据范围:$1\leq n\leq 9$,$1\leq k\leq n*n$。

------------------------------

看到数据范围,不是$dfs$就是状压DP。这道题我们考虑状压DP。

状压DP就是把某个阶段转换成二进制记录下来,一般用于数据范围较小的题目,状压因此得名。

国王个数是一个限制条件,所以这是一个阶段。在状压DP中,我们一般考虑以行作为阶段。因为上一行的放置情况关系到这一行的放置情况,所以我们还要再用一维表示放置情况,这一维要用到状压。

所以我们设$f[i][j][k]$为在前$i$行中放入$k$个国王,且这一行的摆放情况为$j$的方案数。摆放情况可以用$dfs$预先处理。

考虑转移过程中的限制条件:

如果$ sit[j]$ 与 $sit[k]=1 $,则表示上下相邻的格子都摆放了国王。

如果$ (sit[j]<<1)$ 与 $sit[k]=1 $,则表示左上或右下摆放了国王。

如果$ sit[j]$ 与 $(sit[k]<<1)=1 $,则表示右上或左下的格子摆放了国王。

左右相邻的情况在$dfs$中即可排除。

转移方程:$f[i][j][s]+=f[i-1][k][s-num[j]]$。$num[j]$表示放置情况为$j$时国王的放置个数。

边界:$f[1][i][num[i]]=1$。

其实貌似可以用滚动数组优化来省掉第一维,但我懒得写了。

注意开$long long$。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,cnt=0,ans=0;
int sit[2005],num[2005];//预处理放置情况
int f[10][2005][100];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void dfs(int pos,int sum,int node)
{
    if (node>=n)
    {
        sit[++cnt]=pos;
        num[cnt]=sum;
        return;
    }
    dfs(pos,sum,node+1);//在此格不放入国王
    dfs(pos+(1<<node),sum+1,node+2);//在此格放入国王,此时要跳过相邻的格子。
}
signed main()
{
    n=read(),m=read();
    dfs(0,0,0);
    for (int i=1;i<=cnt;i++) f[1][i][num[i]]=1;//边界
    for (int i=2;i<=n;i++)
        for (int j=1;j<=cnt;j++)
            for (int k=1;k<=cnt;k++)
            {
                if (sit[j]&sit[k]) continue;
                if ((sit[j]<<1)&sit[k]) continue;
                if (sit[j]&(sit[k]<<1)) continue;
                for (int s=m;s>=num[j];s--) f[i][j][s]+=f[i-1][k][s-num[j]];//转移
            }
    for (int i=1;i<=cnt;i++) ans+=f[n][i][m];
    printf("%ld",ans);
    return 0;
}

 

posted @ 2020-04-16 22:12  我亦如此向往  阅读(168)  评论(0编辑  收藏  举报