状压dp
作者:互联网
题目大意:农夫有一块地,被划分为m行n列大小相等的格子,其中一些格子是可以放牧的(用1标记),农夫可以在这些格子里放牛,其他格子则不能放牛(用0标记),并且要求不可以使相邻格子都有牛。现在输入数据给出这块地的大小及可否放牧的情况,求该农夫有多少种放牧方案可以选择(注意:任何格子都不放也是一种选择,不要忘记考虑!
#include <cstdio>
#include <cstring>
using namespace std;
#define mod 100000000
int M,N,top = 0;//top表示每行最多的状态数,即如果state中所有状态可行的情况都能种植的话(不是坏土地),则每行状态数就为top
int state[600],num[110]; //state存放每行所有的可行状态(即没有相邻的状态)
int dp[20][600];//dp[i][j]:对于前i行数据,当第i行采用第j种状态时的方案数
int cur[20];//cur[i]表示的是第i行整行的土地情况(但是以相反方式存取的,这样便于与state数组中的状态进行与比较,更方便的判断state中哪些状态是可行的),即哪些可种植,哪些不可种植,二进制表示状态的十进制数
inline bool ok(int x)//判断状态x是否可行
{
if(x&x<<1==0) return true;//若存在相邻两个格子都为1,则该状态不可行 对于二进制数x,只有当x&(x<<1)==0时,二进制数x不会有相邻两个1
return false;
}
void init() //遍历所有可能的状态 /假设两行三列。则遍历 000 001 010 011 100 101 110 111,满足没有相邻1的000 001 010 100 101存进数组state/
{
top = 0;
int total = 1 << N; //一行的最大状态数
for(int i = 0; i < total; ++i)
{
if(ok(i))//如果此状态可行就存进去state数组
state[++top] = i;
}
}
inline bool fit(int x,int y)//判断状态x 与第k行的实际状态的逆是否有‘重合’
{
if(x&y) return true; //若没有重合,(则x符合要求)
return false; //若有重合,则不可行
}
int main()
{
while(scanf("%d%d",&M,&N)!= EOF)
{
init();
memset(dp,0,sizeof(dp));
for(int i = 1; i <= M; ++i)
{
cur[i] = 0;
int num;
for(int j = 1; j <= N; ++j) //输入时就要按位来存储,cur[i]表示的是第i行整行的土地情况,每次改变该数字的二进制表示的一位
{
scanf("%d",&num); //表示第i行第j列的情况(0或1)
if(num == 0) //若该格为0
cur[i] +=(1<<(N-j)); //则将该位置为1(注意要以相反方式存储,即1表示不可放牧 即若输入第一行是111 存进去的cur[1]二进制数为000,便于下面的在state数组中筛选
}
}
for(int i = 1;i <= top;i++)
{
if(fit(state[i],cur[1]))//判断所有可能状态与第一行的 "实际状态的逆" 是否有重合,即初始化
{
dp[1][i] = 1; //若输入的第1行的状态与第i种可行状态吻合,则dp[1][i]记为1
}
}
/*
状态转移过程中,dp[i][k] =Sigma dp[i-1][j] (j为符合条件的所有状态)
*/
for(int i = 2; i <= M; ++i)//i索引第2行到第M行
{
for(int k = 1; k <= top; ++k)//该循环针对所有可能的状态,找出一组与第i行相符的state[k]
{
if(!fit(state[k],cur[i]))//判断是否符合第i行实际情况,不符合的话直接下一循环,每次找到一个符合的再往下判断
continue;
for(int j = 1; j <= top ;++j)//找到state[k]后,再找一组与第i-1行符合,且与第i行(state[])不冲突的状态state[j]
{
if(!fit(state[j],cur[i-1]))//判断是否符合第i-1行实际情况
continue;
if(!fit(state[k],state[j])) //判断是否与第i行冲突
continue;
dp[i][k] = (dp[i][k] +dp[i-1][j])%mod; //若以上皆可通过,则将'j'累加到‘k'上 /即对于前i行,第i行选用第j状态时得到的方案数
}
}
}
int ans = 0;
for(int i = 1; i <= top; ++i)
{
ans = (ans + dp[M][i])%mod; //累加最后一行所有可能状态的值,即得最终结果!!!泥马写注释累死我了终于写完了!
}
printf("%d\n",ans);
}
}
/*总思路是:1.先进行init函数与ok函数 找出没有(有些土地不能种植)这一条件时,对一行中所有满足条件(相邻土地不能一块种植)的状态,用二进制数表示,十进制数存进state数组
2.用一个数组cur存入每一行给出的土地能否种植的状态,但存储方式为相反存储,如若某一行为1 1 1,代表均可放牧,则存进去cur时二进制数为0 0 0
在cur二进制数表示的状态中1表示不可放牧,0表示可放牧
3.然后需要初始化第一行,通过fit函数,就可以判断出state数组中的哪些状态是符合第一行土地要求的,符合要求的i状态dp[1][i]=1;
4.初始化完第一行后,开始从第二行开始遍历直到第M行,每次在state数组中找到一个满足本行土地要求的一个状态,然后再在state中找到一个满足上一行土地要求的一个状态
如果两个状态不冲突的话(位与=0),那么就说明此行的状态可取,更新dp[i][k]+=dp[i-1][j] 其中k为当前行的一个可行状态,j为上一行的一个可行状态
5.遍历完之后,答案就为 Sigma dp[M][i] i为第M行符合条件的所有状态
*/
例题:炮兵阵地
司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
Input
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者’H’),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
6
题解
#include
#include
#include
using namespace std;
int n,m,tot,res; //n为行数,m为列数,tot为合法状态数,res为答案
int a[101]; //将地图每一行压缩为一个二进制数,用十进制存储
int state[65]; //存储所有合法状态
int num[65]; //存储所有合法状态对应的炮台数
int dp[101][65][65]; //dp[i][j][k]表示第i行第j种状态通过上一行第k种状态得到的最大炮台数
int get(int x) //求每种状态对应的炮台数
{
int ans=0;
for (int i=1;i<=m;i++)
if ((x&(1<<(i-1)))!=0) //判断当前位是否为1,若是则炮台数加1
ans++;
return ans;
}
void build() //预处理出所有合法状态
{
for(int i=0;i<(1<<m);i++)
{
if((i&(i<<1))==0&&(i&(i<<2))==0)
{
state[++tot]=i;
num[tot]=get(i);
}
}
}
int main() //主过程
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
char c;
cin>>c;
if (c=='H') a[i]=a[i]|(1<<(j-1)); //若为山地,当前位改为1
}
build();
if(n>=1)
for (int i=1;i<=tot;i++) //对第一行进行处理,不用判断上一行
if ((state[i]&a[1])==0)
{
dp[1][i][0]=max(dp[1][i][0],num[i]);
res=max(dp[1][i][0],res);
}
if(n>=2)
for (int i=1;i<=tot;i++) //对第二行进行处理,只用判断上一行
if ((state[i]&a[2])==0)
for (int j=1;j<=tot;j++)
if ((state[j]&a[1])==0)
{
dp[2][i][j]=max(dp[2][i][j],dp[1][j][0]+num[i]);
res=max(dp[2][i][j],res);
}
for (int now=3;now<=n;now++) //对后n-2行进行处理,要判断前一行和前两行是否合法或冲突
for (int i=1;i<=tot;i++)
if ((state[i]&a[now])==0)
for (int j=1;j<=tot;j++)
if ((state[j]&a[now-1])==0)
for (int k=1;k<=tot;k++)
if ((state[k]&a[now-2])==0)
{
if ((state[i]&state[j])!=0||(state[j]&state[k])!=0||(state[i]&state[k])!=0) continue; //判断上一行和上上行同列有无冲突炮台
dp[now][i][j]=max(dp[now][i][j],dp[now-1][j][k]+num[i]);
res=max(dp[now][i][j],res);
}
printf("%d",res);
}
我写的题解
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=1e2+5;
int n,m,tot,res;
int a[maxn],state[maxn],num[maxn],dp[maxn][maxn][maxn];
int get(int x)
{
int ans=0;
for(int i=1;i<=m;i++)
{
if(((1<<(i-1))&x)!=0)
ans++;
}
return ans;
}
/*void build_state()
{
int total=1<<m;//一行中的最大状态数
for(int i=0;i<total;++i)
{
if((i&(i<<1))==0&&(i&(i<<2))==0)//满足此条件说明此状态满足与1距离2以内不会有1存在
{
state[++tot]=i;//存取十进制数
num[tot]=0;
for(int j=1;j<=state[tot];j=(j<<1))
{
if(j&state[tot])//判断state[most]的二进制数有多少个1
++num[tot]; //计算出该状态炮兵的个数
}
}
}
}*/
void build_state() //预处理出所有合法状态
{
for(int i=0;i<(1<<m);i++)
{
if((i&(i<<1))==0&&(i&(i<<2))==0)
{
state[++tot]=i;
num[tot]=get(i);
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
char c;
cin>>c;
if(c=='H') a[i]=(a[i]|(1<<(j-1)));
}
}
build_state();
if(n>=1)
for(int i=1;i<=tot;i++)
{
if((state[i]&a[1])==0)
{
dp[1][i][0]=max(dp[1][i][0],num[i]);
//if(n==1)
res=max(dp[1][i][0],res);
}
}
if(n>=2)
for(int i=1;i<=tot;i++)
{
if((state[i]&a[2])==0)
{
for(int j=1;j<=tot;j++)
{
if((state[j]&a[1])==0)
{
dp[2][i][j]=max(dp[2][i][j],dp[1][j][0]+num[i]);
//if(n==2)
res=max(res,dp[2][i][j]);
}
}
}
}
for(int now=3;now<=n;now++)
{
for(int i=1;i<=tot;i++)
{
if((state[i]&a[now])==0)
{
for(int j=1;j<=tot;j++)
{
if((state[j]&a[now-1])==0)
{
for(int k=1;k<=tot;k++)
{
if((state[k]&a[now-2])==0)
{
if((state[i]&state[j])==0 && (state[i]&state[k])==0 &&( state[j]&state[k])==0)
{
dp[now][i][j]=max(dp[now][i][j],dp[now-1][j][k]+num[i]);
//if(now==n)
res=max(dp[now][i][j],res);
}
}
}
}
}
}
}
}
cout<<res;
}
标签:状态,炮兵部队,int,状压,state,maxn,include,dp 来源: https://blog.csdn.net/weixin_43310882/article/details/100043805