状压dp的核心,就是把所有的行(不一定)转化成二进制。
其实这跟拼拼图很像。
所以,我们一定要熟悉位操作,今天就是因为不熟悉位运算,浪费了很多时间。
类 型 | 运算符 | 含义 |
位逻辑 运算符 | & | 按位与 |
| | 按位或 | |
^ | 按位异或 | |
~ | 取反 | |
移位运 算 符 | << | 左移 |
>> | 右移 |
总结起来说,就是先把它写成二进制(8位),然后按下表规则进行运算
类型 | 符号 | 规则 | 例子 |
按位与 | & | 同1为1,其余为0 | 9 00001001 & 5 00000101 1 00000001 |
按位或 | | | 同0则0,其余为1 | | 5 00000101 13 00001101 |
按位异或 | ^ | 不同则1,相同则0 | ^ 5 00000101 12 00001100 |
取反~ | ~ | 0变1,1变0 | 9 00001001 ~ 246 11110110 |
左移 | << | 丢掉最右,整体右移 |
>> 2 00000010 |
右移 | >> | 丢掉最左,整体左移 | 9 00001001 << 2 00100100 |
还有,状压dp和dfs在很多题目都有联系!!!!
不多说:
T1:
•题目大意:
在一个n*m(1<=n,m<=12)的田地中种玉米,有些格子不能放,要求不能有任何两个玉米相邻,问有多少种方法(方法数mod 100000000)。
Sample input
2 3
1 1 1
0 1 0
Sample output
9
•例如:11001 表示这一行第1、4、5个格子种了玉米。
so,方程就可以推出了!
我们发现,这次第i行的状态不仅会受到第i-1行的影响,还会受到i-2行的影响,所以我们要多加了一重循环来枚举第i-2行的状态,dp也要增加一维,好在按题中的影响范围,枚举状态后发现即使m=10也只有60种状态满足,因此100×60×60×60不会超时,且100×60×60也开的下。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int n,m,sta[65];
long long dp[105][65][65];
int map[105];
int sum[65];
char s[15];
int pd(int x)
{
if(((x&(x<<2))!=0)||((x&(x<<1))!=0))
return 0;
return 1;
}
int main()
{
int n,m;
string st;
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>st;
for(int j=0;j<m;j++)
{
if(st[j]=='H')
map[i]+=(1<<j);
}//不能放的为1,同下面要放的是1不同,方便做题,因为只要一个&就可以判断方案是否可行!
}
int tot=0;
for(int i=0;i<(1<<m);i++)
{
if(pd(i))
{
sta[tot]=i;
int x=i,temp=0;
while(x>0)
{
temp++;
x-=x-(x&(x-1));
}
sum[tot++]=temp;
}
}
for(int i=0;i<tot;i++)
{
if((map[0]&sta[i])==0)
dp[0][i][0]=sum[i];
}
for(int i=1;i<n;i++)
{
for(int j=0;j<tot;j++)
{
if((sta[j]&map[i-2])!=0)
continue;
for(int k=0;k<tot;k++)
{
if((sta[k]&map[i-1])!=0)
continue;
if((sta[k]&sta[j])!=0)
continue;
for(int l=0;l<tot;l++)
{
if((sta[l]&map[i])!=0)
continue;
if((sta[l]&sta[j])!=0)
continue;
if((sta[l]&sta[k])!=0)
continue;
dp[i][l][k]=max(dp[i][l][k],dp[i-1][k][j]+sum[l]);//j,k,l是sta[]的下标,又是dp的下标,这是一种方法
}
}
}
}
long long ans=0;
for (int i=0;i<tot;i++)
for (int j=0;j<tot;j++)
{
if ((map[n-2]&sta[i])!=0)continue;
if ((map[n-1]&sta[j])!=0)continue;
if ((sta[i]&sta[j])!=0)continue;
ans=max(dp[n-1][j][i],ans);
}
cout<<ans<<endl;
return 0;
}
so,方程就可以推出了!
• f[i,j]表示第i行状态为j的可能性数
• f[i,j]=sum(f[i-1,k])(注意,j和k在上下也要符合条件,要判断)
这道题目,我做了很久,因为我一直想记录每一列的竖砖空位数,但是,其实不需要。其格式同题一差不多,也要判断上下,也要判断左右是否符合。f[i][j][k]最好为第i行状态为j且上一行状态为k,但我不是......
我的方法,(麻烦)
#include<iostream>
using namespace std;
long long num[12][144][2048];
int pd1[2048];
int cf[12],w[2048];
int main()
{
int m,n;
for(;;)
{
int g=0;
cin>>n>>m;
if(m==0&&n==0)
{
break;
}
if(m>n)
{
swap(n,m);
}
if(n==1)
{
if(m%2==0)
cout<<m/2<<endl;
else
cout<<0<<endl;
continue;
}
memset(num,0,sizeof(num));
memset(pd1,0,sizeof(pd1));
memset(cf,0,sizeof(cf));
cf[0]=1;
for(int i=1;i<=m;i++)
{
cf[i]=cf[i-1]*2;
}
int m2=1;
for(int i=0;i<m;i++)
{
m2*=2;
}
for(int i=0;i<m2;i++)
{
int temp=0;
while(i>=cf[temp])
{
temp++;
}
w[i]=temp;
}
for(int i=0;i<cf[m-1]*2;i++)
{
int x=i;
bool bo=true;
int ox=0,cx=0;
for (;x>0;x-=(x-(x&(x-1))))
{
ox=cx;
cx=(x&(-x));
if(ox==0)
{
int temp2=cx;
while(temp2>=4)
{
temp2/=4;
}
if(temp2!=1)
bo=false;
}
if(ox!=0)
{
int temp1=cx/ox;
while(temp1>=4)
{
temp1/=4;
}
if(temp1!=2)
bo=false;
}
}
if((m-w[i])%2!=0)
bo=false;
if(bo==true)
{
pd1[g++]=i;
}
}
for(int j=0;j<g;j++)
{
num[0][j][pd1[j]]++;
}
for(int i=1;i<n-1;i++)
{
for(int j=0;j<g;j++)
{
for(int k=0;k<m2;k++)
{
if(num[i-1][j][k]>0)
{
for(int l=0;l<g;l++)
{
if(((pd1[j]^pd1[l])>0)&&((pd1[j]^k)>0)&&((k&(pd1[j]^pd1[l]))>0))
continue;
num[i][l][k^pd1[l]]+=num[i-1][j][k];
}
}
}
}
}
for(int i=0;i<g;i++)
{
for(int j=0;j<m2;j++)
{
for(int k=0;k<g;k++)
{
if(((pd1[i]^pd1[k])>0)&&((pd1[i]^j)>0)&&((j&(pd1[i]^pd1[k]))>0))
continue;
int temp=pd1[k]^j;
if(temp==0)
num[n-1][k][0]+=num[n-2][i][j];
}
}
}
long long ans=0;
for(int i=0;i<g;i++)
{
ans+=num[n-1][i][0];
}
cout<<ans<<endl;
}
return 0;
}
他人的方法:
#include<iostream>
using namespace std;
int n,m;
long long f[13][13][2060];
int main()
{
cin>>n>>m;
while(n!=0&&m!=0)
{
if(n*m%2==1){cout<<0<<endl;cin>>n>>m;continue;}
f[n+1][1][0]=1;
for(int i=n;i>0;i--)
for(int j=m+1;j>0;j--)
for(int s=(1<<m)-1;s>=0;s--)
{
f[i][j][s]=0;
if(j==m+1){f[i][j][s]=f[i+1][1][s];continue;}
int p=m-j;
if(((1<<p)&s)>0){f[i][j][s]=f[i][j+1][s-(1<<p)];continue;}
f[i][j][s]=f[i][j+1][s+(1<<p)];
if(p>0)
if(((1<<(p-1))&s)==0)f[i][j][s]+=f[i][j+2][s];
}
cout<<f[1][1][0]<<endl;
cin>>n>>m;
}
return 0;
}
。
T3
#include<iostream>
using namespace std;
bool ma[15][15];
long long num[13][4097];
int pd1[4097];
int cf[13],mh[13];
int main()
{
int n,m,m2=1;
cin>>n>>m;
cf[0]=1;
for(int i=1;i<m;i++)
{
cf[i]=cf[i-1]*2;
}
for(int i=0;i<m;i++)
{
m2*=m;
}
int g=0;
for(int i=0;i<cf[m-1]*2;i++)
{
int x=i;
bool bo=true;
int ox=0,cx=0;
for (;x>0;x-=(x-(x&(x-1))))
{
ox=cx;
cx=(x&(-x));
if(ox*2==cx)//判断是否符合条件。每道状压dp的题目基本上都有这一步!
{
bo=false;
}
}
if(bo==true)
{
pd1[g++]=i;//同样很常见!
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cin>>ma[i][j];
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(ma[i][j]==0)
{
mh[i]+=cf[m-j-1];
}
}
}
for(int j=0;j<g;j++)
{
if(mh[0]&pd1[j])
continue;
num[0][j]++;
}
for(int i=1;i<n;i++)
{
for(int j=0;j<g;j++)
{
if(mh[i]&pd1[j])
continue;
for(int k=0;k<g;k++)
{
if(pd1[j]&pd1[k])
continue;//判断上下
num[i][j]+=num[i-1][k];
}
num[i][j]%=100000000;
}
}
long long ans=0;
for(int i=0;i<g;i++)
{
ans+=num[n-1][i];
}
ans%=100000000;
cout<<ans<<endl;
return 0;
}
T2
•
给出一个W行H列的广场
•
用1*2小砖铺盖,小砖之间互相不能重叠
•
问有多少种不同的铺法?
•
1<=W,H<=11
我的方法,(麻烦)
#include<iostream>
using namespace std;
long long num[12][144][2048];
int pd1[2048];
int cf[12],w[2048];
int main()
{
int m,n;
for(;;)
{
int g=0;
cin>>n>>m;
if(m==0&&n==0)
{
break;
}
if(m>n)
{
swap(n,m);
}
if(n==1)
{
if(m%2==0)
cout<<m/2<<endl;
else
cout<<0<<endl;
continue;
}
memset(num,0,sizeof(num));
memset(pd1,0,sizeof(pd1));
memset(cf,0,sizeof(cf));
cf[0]=1;
for(int i=1;i<=m;i++)
{
cf[i]=cf[i-1]*2;
}
int m2=1;
for(int i=0;i<m;i++)
{
m2*=2;
}
for(int i=0;i<m2;i++)
{
int temp=0;
while(i>=cf[temp])
{
temp++;
}
w[i]=temp;
}
for(int i=0;i<cf[m-1]*2;i++)
{
int x=i;
bool bo=true;
int ox=0,cx=0;
for (;x>0;x-=(x-(x&(x-1))))
{
ox=cx;
cx=(x&(-x));
if(ox==0)
{
int temp2=cx;
while(temp2>=4)
{
temp2/=4;
}
if(temp2!=1)
bo=false;
}
if(ox!=0)
{
int temp1=cx/ox;
while(temp1>=4)
{
temp1/=4;
}
if(temp1!=2)
bo=false;
}
}
if((m-w[i])%2!=0)
bo=false;
if(bo==true)
{
pd1[g++]=i;
}
}
for(int j=0;j<g;j++)
{
num[0][j][pd1[j]]++;
}
for(int i=1;i<n-1;i++)
{
for(int j=0;j<g;j++)
{
for(int k=0;k<m2;k++)
{
if(num[i-1][j][k]>0)
{
for(int l=0;l<g;l++)
{
if(((pd1[j]^pd1[l])>0)&&((pd1[j]^k)>0)&&((k&(pd1[j]^pd1[l]))>0))
continue;
num[i][l][k^pd1[l]]+=num[i-1][j][k];
}
}
}
}
}
for(int i=0;i<g;i++)
{
for(int j=0;j<m2;j++)
{
for(int k=0;k<g;k++)
{
if(((pd1[i]^pd1[k])>0)&&((pd1[i]^j)>0)&&((j&(pd1[i]^pd1[k]))>0))
continue;
int temp=pd1[k]^j;
if(temp==0)
num[n-1][k][0]+=num[n-2][i][j];
}
}
}
long long ans=0;
for(int i=0;i<g;i++)
{
ans+=num[n-1][i][0];
}
cout<<ans<<endl;
}
return 0;
}
他人的方法:
#include<iostream>
using namespace std;
int n,m;
long long f[13][13][2060];
int main()
{
cin>>n>>m;
while(n!=0&&m!=0)
{
if(n*m%2==1){cout<<0<<endl;cin>>n>>m;continue;}
f[n+1][1][0]=1;
for(int i=n;i>0;i--)
for(int j=m+1;j>0;j--)
for(int s=(1<<m)-1;s>=0;s--)
{
f[i][j][s]=0;
if(j==m+1){f[i][j][s]=f[i+1][1][s];continue;}
int p=m-j;
if(((1<<p)&s)>0){f[i][j][s]=f[i][j+1][s-(1<<p)];continue;}
f[i][j][s]=f[i][j+1][s+(1<<p)];
if(p>0)
if(((1<<(p-1))&s)==0)f[i][j][s]+=f[i][j+2][s];
}
cout<<f[1][1][0]<<endl;
cin>>n>>m;
}
return 0;
}
。
T3
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
Input
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。
Output
仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。
Sample Input
5 4 PHPP PPHH PPPP PHPP PHHP
Sample Output
6
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int n,m,sta[65];
long long dp[105][65][65];
int map[105];
int sum[65];
char s[15];
int pd(int x)
{
if(((x&(x<<2))!=0)||((x&(x<<1))!=0))
return 0;
return 1;
}
int main()
{
int n,m;
string st;
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>st;
for(int j=0;j<m;j++)
{
if(st[j]=='H')
map[i]+=(1<<j);
}//不能放的为1,同下面要放的是1不同,方便做题,因为只要一个&就可以判断方案是否可行!
}
int tot=0;
for(int i=0;i<(1<<m);i++)
{
if(pd(i))
{
sta[tot]=i;
int x=i,temp=0;
while(x>0)
{
temp++;
x-=x-(x&(x-1));
}
sum[tot++]=temp;
}
}
for(int i=0;i<tot;i++)
{
if((map[0]&sta[i])==0)
dp[0][i][0]=sum[i];
}
for(int i=1;i<n;i++)
{
for(int j=0;j<tot;j++)
{
if((sta[j]&map[i-2])!=0)
continue;
for(int k=0;k<tot;k++)
{
if((sta[k]&map[i-1])!=0)
continue;
if((sta[k]&sta[j])!=0)
continue;
for(int l=0;l<tot;l++)
{
if((sta[l]&map[i])!=0)
continue;
if((sta[l]&sta[j])!=0)
continue;
if((sta[l]&sta[k])!=0)
continue;
dp[i][l][k]=max(dp[i][l][k],dp[i-1][k][j]+sum[l]);//j,k,l是sta[]的下标,又是dp的下标,这是一种方法
}
}
}
}
long long ans=0;
for (int i=0;i<tot;i++)
for (int j=0;j<tot;j++)
{
if ((map[n-2]&sta[i])!=0)continue;
if ((map[n-1]&sta[j])!=0)continue;
if ((sta[i]&sta[j])!=0)continue;
ans=max(dp[n-1][j][i],ans);
}
cout<<ans<<endl;
return 0;
}