状压DP入门题

位运算

学习状压之前必须要熟练掌握位运算

位运算名符号效果
&(and)按位与如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
l(or)按位或两个相应的二进制位中只要有一个为1,该位的结果值为1
^(xor)按位异或(单身狗操作)若参加运算的两个二进制位值相同则为0,否则为1
~取反一元运算符,用来对一个二进制数按位取\反,即将0变1,将1变0
<<左移用来将一个数的各二进制位全部左移N位,右补0
>>右移将一个数的各二进制位右移N位,移到右端 的低位被舍弃,对于无符号数,高位补0

下面是一些基本操作
在这里插入图片描述

注意事项!!!

动态规划必须满足无后效性原则,即则此阶段以后过程的发展变化仅与此阶段的状态有关,而与过程在此阶段以前的阶段所经历过的状态无关。通俗来讲也就是说i的状态只能由i-1来决定

1.[USACO06NOV]玉米田Corn Fields

题目描述
农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

输入输出格式

输入格式:
第一行:两个整数M和N,用空格隔开。

第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。

输出格式:
一个整数,即牧场分配总方案数除以100,000,000的余数
输入输出样例
输入样例#1:

2 3
1 1 1
0 1 0

输出样例#1:

9
代码
#include<bits/stdc++.h>
#define mod 100000000
using namespace std;
const int M=15,N=1<<15;
int st[N],mp[M]//存可行的不相邻的状态以及图中每排的状态;
int dp[M][N];//存每排
int n,m;
int ans;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f; 
}
bool judge1(int x)//判断相邻两点是否冲突
{
	return (x&(x<<1));
}
bool judge2(int i,int x)//判断点能不能放
{
	return (mp[i]&st[x]);
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
	{
	for(int j=1;j<=m;j++)
	{
		int a=read();
		if(a==0)  
        mp[i]+=(1<<(j-1)); 
        /*
        相当于无符号取反操作如将0 1 0变为1 0 1。
        在判断能不能放做准备.想想为什么。
        */
    }
    }
	int k=0;
	for(int i=0;i<(1<<m);i++)
	{
		if(!judge1(i))
		st[++k]=i;
	}
	for(int i=1;i<=k;i++)
	{
		if(!judge2(1,i))
		dp[1][i]=1;
	}
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=k;j++)
		{
			if(judge2(i,j))
			continue;
			for(int f=1;f<=k;f++)
			{
				if(judge2(i-1,f))continue;//剪枝
				if(!(st[j]&st[f]))//判断上下是否冲突
				dp[i][j]+=dp[i-1][f];	
			}
		}
	} 
	for(int i=1;i<=k;i++)
	{
		ans+=dp[n][i];
		ans%=mod; 
	}
	printf("%d",ans);
	return 0;
}

2.P1896 [SCOI2005]互不侵犯

题目描述

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

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

输出格式:
所得的方案数
输入输出样例
输入样例#1:
3 2
输出样例#1:
16

代码
#include<bits/stdc++.h>
using namespace std;
const int N=10,M=1<<10;
int st[M];//表示所有状态 
long long f[N][M][N*N];//表示第i行,状态为j,前面摆了k个国王时,方案数;
int king[M];//表示每个状态所对应的国王数 
int n,k;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
bool judge(int x) //两个国王之间必须隔一个,判断是否满足题意国王之间不相互攻击; 
{
	return x&(x<<1);
}
int main()
{
	n=read();k=read();
	int t=0;
	for(int i=0;i<(1<<n);i++)
	{
		if(!judge(i))
		{
			st[++t]=i;
			int a=i;
			while(a)
			{
				king[t]+= a%2;
				a>>=1;
			}//判断这个状态有多少个国王,也就是t在二进制下有多少个1;
		}
	}//找出所有合法的状态以及该状态国王的数量
	for(int i=1;i<=t;i++)
	if(king[i]<=k)
	f[1][i][king[i]]=1;//先处理第一行; 
	for(int i=2;i<=n;i++)//处理剩下的,所以从 2 开始枚举;
	for(int j=1;j<=t;j++) //枚举当前行状态; 
	for(int l=1;l<=t;l++)//再一遍状态,用来当作上一行的状态,因为有效状态转移是从上一行开始的; 
	{
		if((st[j]<<1)&st[l])continue;//本行的左上角不能有国王; 
		if((st[j]>>1)&st[l])continue;//本行的右上角不能有国王; 
		if(st[j]&st[l])continue;//本行上面不能有国王
		for(int o=1;o<=k;o++)
		{
			if(king[j]+o>k)continue;//国王数量是有限的!! 
			f[i][j][king[j]+o]+=f[i-1][l][o];//f[i][j][k]中的k表示已经用过的国王数,而king[]是本行的,s是本行以前的
		} 
	}
	long long ans=0;
	for(int i=1;i<=n;i++)//不确定在哪一行用光国王,所以都枚举一遍;
	for(int j=1;j<=t;j++)
	ans+=f[i][j][k];//本行及以前用光了国王,那么方案数加在总数中; 
	printf("%lld",ans);
	return 0;
}
`

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值