状态压缩dp常见模型

一般处理方式

所谓状态压缩dp,就是用二进制表示状态,再转换成十进制,方便存储
例如有一排灯,开着的可以用 1 表示,关着的可以用 0 表示,
1011表示第1,3,4灯亮着,2 关着,所以这种状态可以用一个十进制11表示这种状态,进而遇到具体情况进行其他操作。
一般进行操作的模板:

  1. 暴力搜索所有的行的状态,将合法的行筛进state(vector)中
  2. 根据题意,将相邻行之间的所有合法配对方式存到二维数组中
  3. 根据题意进行dp

例题一:小国王

在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
输入格式
共一行,包含两个整数 n 和 k。
输出格式
共一行,表示方案总数,若不能够放置则输出0。
数据范围
1≤n≤10,
0≤k≤n2
输入样例
3 2
输出样例:
16

#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=21;
const int M=1<<10,K=110;
LL f[N][K][M];
int cnt[M];
vector<int>state;
vector<int>head[M];
int n,m;
bool check(int state)//判断每一行的状态是否合法 
{
	for(int i=0;i<n;i++)
	{
		if((state>>i&1)&&(state>>i+1&1))//每一行左右相邻的格子不能有国王 
		return false;
	}
	return true;
}
int count(int state)//二进制中1的个数,即每一行国王的数量 
{
	int res=0;
	for(int i=0;i<n;i++)
	{
		res+=state>>i&1;
	}
	return res;
}
int main()
{
	cin>>n>>m;
	for(int i=0;i<1<<n;i++)
	{
		if(check(i))
		{
			state.push_back(i);//筛选合理的行 
			cnt[i]=count(i);
		}
	}
	for(int i=0;i<state.size();i++)
	{
		for(int j=0;j<state.size();j++)
		{
			int a=state[i],b=state[j];
			if(!(a&b)&&check(a|b))
			{
				head[a].push_back(b);//筛选上下相邻合格的两行
			}
		}
	}
	f[0][0][0]=1;
	for(int i=1;i<=n+1;i++)
	{
		for(int j=0;j<=m;j++)
		{
			for(auto x:state)
			{
				for(auto y:head[x])
				{
					if(j>=cnt[x])
					{
						f[i][j][x]+=f[i-1][j-cnt[x]][y];
					}
				}
			}
		}
	}
	cout<<f[n+1][m][0]<<endl;
	return 0;
}

例题二:玉米田

农夫约翰的土地由 M×N 个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是不育的,无法种植。
而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
现在给定土地的大小,请你求出共有多少种种植方法。
土地上什么都不种也算一种方法。
输入格式
第 1 行包含两个整数 M 和 N。
第 2…M+1 行:每行包含 N 个整数 0 或 1,用来描述整个土地的状况,1 表示该块土地肥沃,0 表示该块土地不育。
输出格式
输出总种植方法对 108 取模后的值。
数据范围
1≤M,N≤12
输入样例:
2 3
1 1 1
0 1 0
输出样例:
9

#include <bits/stdc++.h>
using namespace std;
const int N=15;
const int M=1<<12;
const int mod=1e8;
int w[N];
vector<int>state;
vector<int>head[M];
int f[N][M];//第i行状态为j
int n,m;
bool check(int state)
{
	for(int i=0;i<m;i++)
	{
		if((state>>i&1)&&(state>>i+1&1))
		return false;
	}
	return true;
} 
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<m;j++)
		{
			int t;
			cin>>t;
			w[i]+=!t*(1<<j);
		}
	}
	for(int i=0;i<1<<m;i++)
		if(check(i))state.push_back(i);
	for(int i=0;i<state.size();i++)
	{
		for(int j=0;j<state.size();j++)
		{
			int a=state[i],b=state[j];
			if(!(a&b))head[a].push_back(b);
		}
	}
	f[0][0]=1;
	for(int i=1;i<=n+1;i++)
	{
		for(auto x:state)
		{
			if(!(x&w[i]))//筛选合法的状态,当前层能种植0,不能种植1,枚举状态的1是可以种植,0不能种植,&运算为0为合法方案
			{
				for(auto y:head[x])
				{
					f[i][x]=(f[i][x]+f[i-1][y])%mod;
				} 
			} 
		}
	}
	cout<<f[n+1][0]<<endl;
	return 0;
}

例题三:炮兵阵地

司令部的将军们打算在 N×M 的网格地图上部署他们的炮兵部队。
一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地(用 H 表示),也可能是平原(用 P 表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
在这里插入图片描述

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
图上其它白色网格均攻击不到。
从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示 N 和 M;
接下来的 N 行,每一行含有连续的 M 个字符(P 或者 H),中间没有空格。按顺序表示地图中每一行的数据。
输出格式
仅一行,包含一个整数 K,表示最多能摆放的炮兵部队的数量。
数据范围
N≤100,M≤10
输入样例
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出样例:
6

#include <bits/stdc++.h>
using namespace std;
const int N=10,M=1<<10;
int n,m;
int g[1010];
int f[2][M][M];
vector<int>state;
vector<int>head[M];
int cnt[N];
bool check(int state)
{
    return !(state&state>>1||state&state>>2);
}
int count(int state)
{
	int res=0;
	for(int i=0;i<m;i++)
	{
		res+=state>>i&1;
	}
	return res;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<m;j++)
		{
			char c;
			cin>>c;
			g[i]+=(c=='H')<<j;
		}
	}
	for(int i=0;i<1<<m;i++)
	{
		if(check(i))
		{
			state.push_back(i);
			cnt[i]=count(i);
		}
	}
    for (auto x : state)
        for (auto y : state)
            if (!(x & y))
                head[x].push_back(y);
    for (int i = 1; i <= n + 2; i++)
        for (auto x : state)
            if (!(x & g[i]))
                for (auto y : head[x])
                    for (auto z : head[y])
                        if (!(x & z))
                            f[i&1][x][y] = max(f[i&1][x][y], f[i - 1&1][y][z] + cnt[x]);
    cout<<f[n+2&1][0][0]<<endl;
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leimingzeOuO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值