位运算
学习状压之前必须要熟练掌握位运算
位运算名 | 符号 | 效果 |
---|---|---|
&(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;
}
`