基础状压dp举例

在处理很多dp问题时,我们需要用状态转移来完成我们的目标,但是某些时候我们很难去描述一个状态,所以我们需要用一些手段进行状态压缩,其中最基础的状态压缩就是用2进制来表示状态,下面介绍2个例题

[SCOI2005]互不侵犯KING
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。
国王能攻击到它上下左右,以及左上 左下右上右下八个方向上附近的各一个格子,共8个格子。
输入描述:
只有一行,包含两个数N,K ( 1 ≤ N ≤ 9, 0 ≤ K ≤ N * N)
输出描述:
方案数。
很显然我们的每一个国王会影响其他位置的放置情况,如果直接搜索,会有81层递归,一定会tle,而用dp的思路来看如果一个个格子的去推进,并不满足无后效性,所以在进行dp的过程中,我们需要一行行的进行转移 那么第一个问题就是如何表示每一行的状态了…… 由于每一行的格子小于等于9个,每一个格子又只有两种状态——放国王和不放国王,假设 我们把放了国王记为1,没有放国王记为0,很显然每一行的状态就是一个01串,如果把这 个串看成一个二进制数,那么它显然不会超过(111111111),即2^9-1,• 那么我们就完全可以拿一个int类型的数来表示每一行的状态。然后,怎样的状态才是合法的呢? 没有相邻的两个1! 也就是 (x & (x<<1))==0 ,我们需要找出所有当前行合法的状态,往下转移状态
状态转移方程就是• f[i][j][k]表示第i行的状态为j 且已经放了k个国王的方案数
如果当前行状态和上一行状态不冲突 f[i+1][l][k+cnt[l]]+=f[i][j][k];就加入可能性
通过枚举行数和2行的状态和国王数,即可完成dp过程,得到所有状态的方案数总和
本题在代码中使用了数组保存某些限制

#include
using namespace std;
long long ans;
const int S=(1<<10)+5;
int cnt[S],g[S],h[S];
long long f[10][S][100];
int main()
{
int n,z;
    cin>>n>>z;
    int q=1<<n;
    f[0][0][0]=1;
    for(int i=0;i<q;i++){
        g[i]=((i&(i<<1))==0)&&((i&(i>>1))==0);//保证左右没有相同状态时为1
        h[i]=i|(i>>1)|(i<<1);     //表示该状态所能被上下一行影响的辐射范围,即我为1上下一行不能为1
        cnt[i]=cnt[i>>1]+(i&1);  //存储当前状态一的个数即国王个数
    }
   for(int i=0;i<n;i++)
       for(int j=0;j<q;j++)
           for(int k=0;k<=z;k++)
               if(f[i][j][k])//如果为0则没有加的必要
                   for(int l=0;l<q;l++)
                       if(g[l]&&(h[l]&j)==0)//本身状态没问题,且不和上一行状态冲突
                           f[i+1][l][k+cnt[l]]+=f[i][j][k];
for(int i=0;i<q;i++){
        ans+=f[n][i][z];
    }
    printf("%lld\n",ans);
return 0;
}

炮兵阵地
司令部的将军们打算在NM的网格地图上部署他们的炮兵部队。一个NM的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入描述:
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符(‘P’或者’H’),中间没有空格。按顺序表示地图中每一行的数据。N≤100;M≤10。
输出描述:
一行,包含一个整数K,表示最多能摆放的炮兵部队的数量
这道题和上道题相比一是每个棋子能影响范围不同,并且原始转态中多了一些限制,而且没有限制棋子的数目,我们需要尽可能多的放置炮兵
第一步:确定状态
由于每一个炮都可以打到两行,所以每一行的放置方法都与他放置的情况有关 所以 f[i][j][k] 表示第i行为状态j,第i - 1行为状态为k时所用的最大炮兵数
第二步:确定状态转移方程
所以 f[i][j][p] = max(f[i][j][p], f[i - 1][p][q] + num[j]) q和p和j均不发生冲突 Pqj均为符合要求的状态,即任意1左右两边两位都不是1判断条件是((i & (i >> 1)) == 0) && ((i & (i >> 2)) == 0),且为1的地方都是平原,并且前两行的状态都不与我冲突
由于数据范围较大,我们尽可能去压缩空间,在这个推进过程中,我们可以通过0和1来表示上一行是奇数行或偶数行,在递推过程中也可以完成数据的累积过程。节省了大量空间
在递推过程中我们需要枚举上一行状态上上行状态和当前行状态确保可以不被其他棋子干扰
代码如下

#include
using namespace std;
const int MAXN=1030;
int dp[2][MAXN][MAXN];  //dp[i][j][k]表示第i行状态为j和第i-1行状态为k时所用的最大炮兵数 01表示当前行奇偶
int state[MAXN],cnt=0,g[MAXN],num[MAXN]; 
int n,m;
int count(int x)         //计算1的个数 
{
	int i;
	int ans=0;
	for(i=0;i<m;i++){
		if((x>>i)&1) ans++;
	}
	return ans;
}
int main()
{
	int i,j,k,h;
	scanf("%d %d",&n,&m);
	for(i=1;i<=n;i++){
		for(j=0;j<m;j++){
			char fu;
			cin>>fu;
			g[i]+=(fu=='H')<<j;
		}
	}
	for(i=0;i<(1<<m);i++){
		if((!(i&(i>>1)))&&(!(i&(i>>2))))//以为当前状态左右没有1的间距在2之内
           {
			state[++cnt]=i;
			num[cnt]=count(i);
		}
	}
	for(i=1;i<=n;i++){
		for(j=1;j<=cnt;j++){           //这行状态 
			for(k=1;k<=cnt;k++){       //上一行状态 
				for(h=1;h<=cnt;h++){   //上上行状态 
					int a=state[j],b=state[k],c=state[h];
					if((a&b)|(b&c)|(a&c)) continue;    //炮兵十字下面俩格
					if((g[i-1]&b)|(g[i]&a)) continue;   //上一行或上上行的地能否放置部队
					dp[i&1][j][k]=max(dp[i&1][j][k],dp[(i-1)&1][k][h]+num[j]); 
				}
			}
		}
	}
	int ans=0;
	for(i=1;i<=cnt;i++){
		for(j=1;j<=cnt;j++){
			ans=max(ans,dp[n&1][i][j]);
		}
	}
	printf("%d\n",ans);
	return 0;
}

你可能感兴趣的:(dp)