状压dp 学习日志 ---2019.1.21
标签(空格分隔): 状压dp
推荐blog:
【暖*墟】
lead into
位运算
为了更好的理解状压dp,首先介绍位运算相关的知识。
1.x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如3(11)&2(10)=2(10)。
2.x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如3(11)|2(10)=3(11)。
3.xy,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如3(11)2(10)=1(01)。
’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充。
’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最右一位。
常见的应用:
1.判断一个数字x二进制下第i位是不是等于1。
方法:if(((1<<(i−1))&x)>0)
将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。
2.将一个数字x二进制下第i位更改成1。
方法:x=x|(1<<(i−1))
3.把一个数字二进制下最靠右的第一个1去掉。
方法:x=x&(x−1)
例题1
玉米田Corn Fields
/*【洛谷p1879】玉米田
一块长方形的牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12)。
从中选择一些作为草地,但不会选择两块相邻的土地。
如果不考虑草地的总块数,一共有多少种选择方案?(0也是一种方案)。*/
#include<bits/stdc++.h>
#define mod 100000000
using namespace std;
int n,m,tot,state[1500],dp[15][1500],ans,cur[15];
//dp表示当前最大值,第一维是行数,第二维是状态数,cur是每行的情况,state是预存的可能状态
/*这题也是用二进制来表示状态,先预处理:枚举一行内(不考虑地图因素)每一种状态,看看是否合法,合法的话就打个标记,以便后面操作.先处理第一行,枚举下面行的时候再枚举一遍上一行,看看两行放置是否合法,合法就累计上一行计数即可。*/
inline int read()
{
int ans=0;
char last=' ',ch=getchar();
while(ch<'0' || ch>'9') last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans;
return ans;
}
inline bool fit(int x,int k)
{
return !(state[x]&cur[k]);
}
inline void init()
{
for (int i=0;i<(1<<n);i++)
if (!(i&(i<<1)))
state[++tot]=i;
}
int main()
{
m=read();
n=read();
init();
int k;
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
{
k=read();
if (!k)
cur[i]+=(1<<(n-j));
}
for (int i=1;i<=tot;i++)
if (fit(i,1))
dp[1][i]=1;
for (int i=2;i<=m;i++)
for (int j=1;j<=tot;j++)
{
if (!fit(j,i))
continue;
for (k=1;k<=tot;k++)
{
if (!fit(k,i-1))
continue;
if (state[j]&state[k])
continue;
dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
}
}
for (int i=1;i<=tot;i++)
ans=(ans+dp[m][i])%mod;
printf("%d",ans);
return 0;
}
例题2
#include<bits/stdc++.h>
using namespace std;
/*t = statue - (1<<(i-1)) : 代表当前没有到达i店的statue的前一个状态。
d (statue , i) == max( d( t , j ) + m( j, i ) ) : j 代表状态t已经到达的店,也是状态t当前停留的店,+m( j, i) 经过了
这段距离 状态t 到达了状态statue,也就是 t +1<<(i-1) == statue;*/
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(int i=a;i<=b;i++)
int d[1<<12][12];
int m[12][12];
int n;
void Floyd()
{
for(int k=0; k<=n; k++)
for(int i=0; i<=n; i++)
for(int j=0; j<=n; j++)
m[i][j]=min(m[i][j],m[i][k]+m[k][j]);
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=0; i<=n; i++)
for(int j=0; j<=n; j++)
scanf("%d",&m[i][j]);
memset(d,inf,sizeof(d));
Floyd();
d[0][0]=0;
rep(i,1,(1<<n)-1)
for(int i=1; i<(1<<n); i++)
{
for(int j=1; j<=n; j++)
{
if(i&(1<<(j-1)))
{
int t=i-(1<<(j-1));
if(t==0)
{
d[i][j]=m[0][j];
continue;
}
for(int k=1; k<=n; k++)
{
if(t&(1<<(k-1)))
{
d[i][j]=min(d[i][j],d[t][k]+m[k][j]);
}
}
}
}
}
int ans=inf;
for(int i=1; i<=n; i++)
ans=min(ans,d[(1<<n)-1][i]+m[i][0]);
printf("%d
",ans);
}
}
例题3
互不侵犯
注:这是昨年坐过的题
/*【洛谷p1896】互不侵犯
国王能攻击到它上下左右、以及左上左下右上右下八个方向上一个格子。
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。*/
#include<bits/stdc++.h>
using namespace std;
int n,k;
long long dp[10][15000][80],king[15000],state[15000],tot,ans;
//dp[i][j][k]表示第i行选j状态在这一行及之前摆上k个国王的方案总数
//state:状态,king:那一行的国王总数
inline int read()
{
int x=0,f=1;
char c=getchar();
while (!isdigit(c))
f=c=='-'?-1:1,c=getchar();
while (isdigit(c))
x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
inline void init()
{
tot=(1<<n)-1;
for (int i=0;i<=tot;i++)
if (!((i<<1)&i))
{
state[++ans]=i;
int t=i;
while (t) //记录国王个数
king[ans]+=t%2,t>>=1; //注意是右移一位
}
}
int main()
{
n=read(),k=read();
init();
for (int i=1;i<=ans;i++)
if (king[i]<=k)
dp[1][i][king[i]]=1;
for (int i=2;i<=n;i++) //枚举行
for (int j=1;j<=ans;j++) //枚举这行方案
for (int p=1;p<=ans;p++) //枚举上行方案
{
if (state[j]&state[p])continue;
if (state[j]&(state[p]<<1))continue;
if (state[p]&(state[j]<<1))continue;
for (int s=1;s<=k;s++) //枚举国王个数
{
if (king[j]+s>k)continue;
dp[i][j][king[j]+s]+=dp[i-1][p][s];
}
}
for (int i=1;i<=n;i++)
for (int j=1;j<=ans;j++)
tot+=dp[i][j][k];
printf("%lld",tot);
return 0;
}
例题4
炮兵阵地
待填坑