基础状压dp

方格棋盘

题目描述

在n*n(n≤20)的方格棋盘上放置n个车,每个车可以攻击到所在行和列任意一个格,求使它们不能互相攻击的方案总数。

输入格式

一个数 n

输出格式

方案总数

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;

int n,m;
__int128 dp[1 << 21];

void print(__int128 x){
	if(x < 0 ) putchar('-'), x*=-1;
	if(x > 9 ) print( x / 10 );
	putchar( x % 10 + '0' );
	return;
}

int main()
{
	scanf("%d",&n);
	
	dp[0] = 1;
	for (int i = 1; i < ( 1 << n ); ++ i) {
		for (int j = i; j > 0; j -= ( j & - j )) {
			//j&-j表示找最后的一的位数
			//而j-=j&-j表示依次次向前找 i的每个1 的位数 
			dp[i] += dp[i & ~ ( j & - j )];
			//i&~(j&-j)表示在i中 把j所表示的1的位数 上的1 变成0
		} 
	}
	print(dp[( 1 << n ) - 1]);
    //表示n位的二进制数每一位都为1的情况,即每一列都放上了车
	return 0;
}

方格棋盘(加强版)

题目描述

在n*n(n≤20)的方格棋盘上放置n个车,但有m个格子不能放车,每个车可以攻击到所在行和列任意一个格,求使它们不能互相攻击的方案总数。

输入格式

一个数 n,m
下面有m行,每行两个整数,为不能放的格子的位置。

输出格式

方案总数

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;

int n,m;
__int128 dp[1 << 21], hole[1 << 21];
//hole[x]=y表示 第i行中 第y的二进制下1的位数列 不能放车 

void print(__int128 x){
	if(x < 0 ) putchar('-'), x*=-1;
	if(x > 9 ) print( x / 10 );
	putchar( x % 10 + '0' );
	return;
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i = 1, x, y; i <= m; ++ i) {
		scanf("%d%d",&x,&y);
		hole[x] |= ( 1 << ( y - 1 ) ); 
		//这里注意要用|,因为可能有多个y在x行 
	}
	dp[0] = 1;
	for (int i = 1, j, temp; i < ( 1 << n ); ++ i) {
		temp = 0;
		for ( j = i; j > 0; j -= ( j & -j )) {
			temp ++;
			//寻找i所表示的二进制数下一共有多少个1
			//即寻找放了几个车,此时放的车到了第temp行 
		}
		 
		for ( j = i & ( ~ hole[temp] ); j > 0; j -= ( j & -j )) {
			//j = i & ( ~ hole[temp] )表示将本行 不能放车的 列的编号 上的 1变成0
            //因为行数随着列数的增加而增加,所以是从上向下转移dp
            //因为从上向下转移,所以i所表示的新车的放置情况不能在hole[x]上
            //因此在求和时 不加上 没在hole[x]上放车的 就行
            //如:i=11101时hole[temp]为01100
            //则  j为10001
            //所以加上01101和11100即可
            //不能加上10101和11001
			//j&-j表示找最后的1的位数
			//而j-=j&-j表示依次向前找i的1的位数 
			dp[i] += dp[i & ~ ( j & - j )];
			//i&~(j&-j)表示在i中 把j表示的1的位数 上的1 变成0 
		} 
	}
	print(dp[( 1 << n ) - 1]);
	//表示n位都为1的情况,即每一列都放上了车 
	return 0;
}

[SCOI2005] 互不侵犯

题目描述

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

注:数据有加强(2018/4/25)

输入格式

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式

所得的方案数

样例 #1

样例输入 #1

3 2

样例输出 #1

16
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>

using namespace std;

long long dp[10][1 << 10][10 * 10 + 1];
//dp[i][j][k]表示到第i行中的各个国王的位置是bit[j],一共用了k个国王数量
long long bit[1 << 10], kingnum[1 << 10], cnt;
//bit表示表示的国王放置的位置
//kingnum[cnt]表示bit[cnt]放了几个国王
int n, k;

void dfs( int order , int king_number , int current ) {
	//order表示放置的国王位置表示的序列
	//king_number表示道current放了几个国王
	//current表示如果现在的放的话国王的位置
	if ( current > n ) {
		cnt ++;
		bit[cnt] = order;
		kingnum[cnt] = king_number;
		return;
	}
	dfs( order + ( 1 << ( current - 1 ) ) , king_number + 1 , current + 2 );
	dfs( order , king_number , current + 1);
	return;
}

bool compear( int x , int y ) {
	if ( ( ( bit[x] & bit[y] ) == 0 ) && ( ( ( bit[x] << 1 ) & bit[y] ) == 0 ) && ( ( bit[x] & ( bit[y] << 1 ) ) == 0 ) ) {
		//注意&的位运算炒鸡低呜呜呜
		//bit[x]&bit[y]=0表示上一行与这一行的1没有对应的
		//bit[x]<<1 & bit[y]=0表示上一行放的国王的的左下角没有国王
		//bit[x] & bit[y]<<1表示上一行放的国王的右下角没有国王
		//注意不能bit[x]>>1因为会吞掉最右边1为的1
		return true;
	}
	else {
		return false;
	}
}
 
int main()
{
	scanf("%d%d",&n,&k);
	dfs( 0 , 0 , 1 );
	for (int i = 1; i <= cnt; ++ i) {
		dp[1][i][kingnum[i]] = 1;
	}
	for (int line = 2; line <= n; ++ line) {
		//表示第line行
		for (int i = 1; i <= cnt; ++ i) {
			//表示上一行国王位置所表示的cnt
			for (int j = 1; j <= cnt; ++ j) {
				//表示此行国王位置所表示的cnt
				if ( compear( i , j ) ) {
					for (int num = kingnum[j]; num <= k; ++ num) {
						//一共用了num个国王
						dp[line][j][num] += dp[line - 1][i][num - kingnum[j]];
					}
				}
			}
		}
	}
	long long ans = 0;
	for (int i = 1; i <= cnt; ++ i) {
		ans += dp[n][i][k];
	}
	cout<<ans<<endl;
	return 0;
}
posted @ 2023-03-25 11:12  觉清风  阅读(15)  评论(0编辑  收藏  举报