基础状压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;
}