您的位置:首页 > 其它

【SCOI2005】互不侵犯king(状压Dp入门)

2018-03-03 09:15 204 查看

题目链接:题目链接

题目分析:

    我太菜了,也只能写写自己的理解。
    观察题目我们可以发现在n*n的棋盘中,对于每一行上的每一格来说我们都可以用一个2进制位来表示(0,1)

其中0表示这一位不放国王,1表示这一位放国王,然后我们就可以用一个10进制的数来表示这一行的状态,状态就被压缩了。
然后对于n*n的棋盘来说,对于每一行我们只能放n个国王,用状态压缩的思想来,就是n个二进制为全部为1,这也是转化为十进制后十进制最大的一种方案,这就是我们枚举的上限.然后我们先枚举一行所有状态,判断出所有合法的状态用s[]表示,再枚举一边判断任意两个合法的状态是否可以相邻,用map[]表示。
    状态转移方程f[i][j][k]表示已经放了前i行,放了j个国王,当前行为k的方案数#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
const int MAXN=600;
int num[MAXN],map[MAXN][MAXN],s[MAXN],n,k,sum;
long long ans,f[10][MAXN][MAXN];
using namespace std;
void Built_s()
{
int i;
for( i=0;i<sum;i++)//枚举每一行可能出现的状态
{
if((i&(i<<1))==0)//如果这一行的状态合法
{
s[i]=1;//状态合法,记录;
int cnt=0,t=i;
while(t)//记录有几个国王
{
cnt+=(t&1);//当前二进制位有国王为1,没有为0;
t>>=1;// 左移检查当前状态的下一二进制位
}
// cout<<cnt;
num[i]=cnt;//国王数=cnt
f[1][cnt][i]=1;//前i行放cnt个国王当前行的状态为i;
}
}
}
void Built_map()
{
int i,j;
for(i=0;i<sum;i++)
{
if(!s[i]) continue;//如果这个状态不合法,continue;
for(j=0;j<sum;j++)
{
if(!s[j]) continue ;//如果这个状态不合法,continue ;
if(!(i&j)&&(!((i<<1)&j))&&(!((i>>1)&j))) map[i][j]=1;//若合法则可以相邻;
/*
!(i&j)表示状态i与j没有同一二进制位存在国王;
!((i<<1)&j) 表示状态i的左边二进制位与j不同时存在国王
!((i>>1)&j) 表示状态i的右边二进制位于j不同时存在国王
*/
}
}
}
void Dp()
{
int i,j,now;
for(i=2;i<=n;i++)//枚举行数
{
for(j=0;j<=k;j++)//枚举前i行存在的国王
{
for(now=0;now<sum;now++)//枚举每一行的 状态
{
if(!s[now]) continue ;
if(num[now]>j) continue ;
int q;//不符合continue;
for(q=0;q<sum;q++)//枚举上一行的状态
{
if(!map[now][q]) continue ;//要合法
if(!s[q]) continue ;
if(num[q]+num[now]>j) continue ;
f[i][j][now]+=f[i-1][j-num[now]][q];//转移
// cout<<f[i][j][now]<<endl;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&k);
// cout<<n<<k;
sum=1<<n;
// printf("%d",sum);
Find_s();
Find_map;
Dp;
for(int i=0;i<sum;i++)
{
if(!s[i]) continue ;
ans+=f
[k][i];
}
printf("%lld",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: