SCOI2055 互不侵犯(状压dp)
作者:互联网
题目描述
输入描述
输出描述
输入样例
3 2
输出样例
16
数据范围
状态压缩 dp 的又一入门题,第一道能独立写出的状压,感动落泪 ,由于每个国王的攻击范围是周围 8 格,因此对于同一行而言,不能出现摆放相邻国王的情况,且摆放到某一行时,也要考虑是否会被上一行摆放的国王攻击到。
解题过程可参照:蒙德里安的梦想
首先枚举每一行所有可能的状态,并判断以该状态的摆法是否可行。本题中同样以 1 表示该位置摆放了国王,0 则表示没有,因此对于 n * n 的矩阵,显然长度为 n 的行中,可能出现的状态一共有 2n 种。对于这 2n 种状态,判断其是否存在相邻列都为 1 的情况,若有则将该状态标为非法。
由于本题中新增了摆放个数的限制,因此状态上需要新增一维,即 f [ i ][ j ][ k ],其中 i 表示当前为第 i 行,j 表示当前行以第 j 种状态进行摆放,k 表示包括当前行已摆放了多少个国王。在预处理每一种状态是否合法时,注意统计该状态摆放了多少个国王,并用 cnt [ k ] 进行记录。
随后对于每一行,在摆放该行时需要考虑上一行的摆放状态及已摆放的个数。即状态转移方程为:
f [ i ] [ j ] [ k ] + = f [ i − 1 ] [ x ] [ k − c n t [ j ] ] f[i][j][k]+=f[i-1][x][k-cnt[j]] f[i][j][k]+=f[i−1][x][k−cnt[j]]
表示:第 i 行以第 j 种状态进行摆放,且共摆放了 k 个,且该状态是由第 i - 1 行以第 x 种状态进行摆放,且共摆放了 k - cnt [ j ] 转化而来。
注意在枚举状态时,需要判断该状态是否可行,及相邻两行的状态间是否会产生冲突。
参考代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=12,M=1<<10,K=110;
bool st[M];
ll cnt[M];//该状态下共有多少个棋子
ll f[N][M][K];
int main(){
ll n,k;
cin>>n>>k;
//预处理每一行的合法状态
for(ll i=0;i<1<<n;i++){//枚举每种状态
st[i]=true;
ll count=0;
for(ll j=0;j<n;j++){//该状态的每一列
if((i>>j)&1){
if(((i>>(j+1))&1))//判断是否存在相邻情况
st[i]=false;
count++;
}
}
cnt[i]=count;//第i个状态共摆放了count个国王
//cout<<i<<" "<<count<<" "<<st[i]<<endl;
}
memset(f,0,sizeof(f));
f[0][0][0]=1;
for(ll i=1;i<=n+1;i++){
for(ll j=0;j<1<<n;j++){
for(ll x=0;x<1<<n;x++){
if(!st[j]||!st[x]||!st[j|x])
continue;
if((j&x)!=0)
continue;
for(ll l=cnt[j];l<=k;l++){
if(cnt[x]>(l-cnt[j]))
continue;
f[i][j][l]=f[i][j][l]+f[i-1][x][l-cnt[j]];
}
}
}
}
cout<<f[n+1][0][k];
return 0;
}
标签:count,状态,cnt,SCOI2055,ll,状压,摆放,st,dp 来源: https://blog.csdn.net/laysan/article/details/121296192