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[i1][x][kcnt[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;
}

猜你喜欢

转载自blog.csdn.net/laysan/article/details/121296192
今日推荐