jjzjj

动态规划算法刷题笔记【状压dp】

call me by ur name 2024-02-13 原文

二进制枚举子集


a&1 == 1 判断是否为奇数,如果为1,则为奇数因为奇数二进制末位一定是1,所以 与1 得到的结果是1



这里,1<<14——214——第15位是1,可以表示14个1
i&(1<<j)—— 从0开始是因为,原本第1位就是1。所以j=0时,对应的就是 i 的最低位

状态压缩

旅行商问题



F l o y d Floyd Floyd算法:

方格取数问题





n o w ∣ f l a g = = f l a g now | flag == flag nowflag==flag —— (1代表可以选择,0代表不可以选择):

10110 10110 10110
00110 00110 00110
= 10110 = = f l a g = 10110 == flag =10110==flag

10110 10110 10110
01001 01001 01001
= 11111 ! = f l a g = 11111 != flag =11111!=flag

使用条件

  • 状态需要有一定的状态单元。 即一个状态应该是保存一个集合,其中的元素值对应着0或1,例如我们常见的棋盘,我们可以用0或1来表示棋子的放置状态。而整个集合即是一个01串,即二进制数,我们通常用十进制表示。那么我们再进行状态转移或者判断的时候,需要先将十进制转化为二进制,再将二进制转化为十进制
  • 题目中限制的集合大小不会超过20。 如果用二进制表示状态,那么集合大小为20的二进制状态有220-1,已经达到1e7的数量级了
  • 具有动态规划的特性。 对于动态规划,一般都是要求最优化某个值,具有最优子结构的性质。同时也需要满足状态转移的特性,而不是前一个状态毫无关系的

题目

[SCOI2005] 互不侵犯

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

输入格式

只有一行,包含两个数N,K ( 1 < = N < = 9 , 0 < = K < = N ∗ N 1 <=N <=9, 0 <= K <= N * N 1<=N<=9,0<=K<=NN

输出格式

所得的方案数

样例输入 #1

3 2

样例输出 #1

16

思路

  • 还是比较模板的一道题
  • 国王的意思是:选定了格子,然后不能相邻(斜上方、斜下方)。跟方格取数差不多
  • 首先对第1行进行处理,这样后面的行才能使用模板
  • 循环模板:1. 枚举行。——2. 枚举上个阶段放了的国王数。——3. 枚举本阶段的状态。——4. 在枚举本阶段状态的同时,枚举上一阶段的状态,维护状态。

题解

#include<bits/stdc++.h>
using namespace std;
long long ans,n,m,k,f[10][100][1<<9+5];
int lowbit(int x){
	int tmp=0;
	while(x) tmp++,x-=x&(-x);
	return tmp;
}
int main(){
	scanf("%lld%lld",&n,&k);
	m=(1<<n)-1;
	for(int i=0;i<=m;++i){
		if(!(i&(i<<1))&&lowbit(i)<=k) 
		f[1][lowbit(i)][i]=1;
	} 
	//处理出第一行的所有情况 
	for(int i=2;i<=n;++i)//枚举行 
	for(int j=0;j<=k;++j)//枚举上个阶段放了的国王数 
	for(int x=0;x<=m;++x){//枚举本阶段的状态 
		if(x&(x<<1)) continue;//本阶段不能互相伤害 
		for(int y=0;y<=m;++y){//枚举上一个阶段的状态 
			if(x&y||x&(y<<1)||x&(y>>1)) continue;//本状态和上一个状态不能冲突
			if(j+lowbit(x)>k) continue;//本状态新放的国王数目+上个阶段国王数小于k 
			f[i][j+lowbit(x)][x]+=f[i-1][j][y];  //本状态加上一状态数 
		}
	}
	for(int j=0;j<=m;++j) ans+=f[n][k][j];
	printf("%lld",ans);
}

[NOI2001] 炮兵阵地

司令部的将军们打算在 N × M N\times M N×M 的网格地图上部署他们的炮兵部队。

一个 N × M N\times M N×M 的地图由 N N N M M M 列组成,地图的每一格可能是山地(用 H \texttt{H} H 表示),也可能是平原(用 P \texttt{P} P 表示),如下图。

在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。

图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。

现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入格式

第一行包含两个由空格分割开的正整数,分别表示 N N N M M M

接下来的 N N N 行,每一行含有连续的 M M M 个字符,按顺序表示地图中每一行的数据。

输出格式

一行一个整数,表示最多能摆放的炮兵部队的数量。

样例输入 #1

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

样例输出 #1

6

提示

对于 100 % 100\% 100% 的数据, N ≤ 100 N\le 100 N100 M ≤ 10 M\le 10 M10,保证字符仅包含 PH

思路

  • 这道题还是有些复杂的
  • 按照思路:1. 初始化(对第1行操作、枚举合法状态)。2. 状态转移。(循环判断后面的行是否合法——判断 i-1,i-2)

题解

#include<bits/stdc++.h>
using namespace std;
int n,m,num[60];
char s[110][15]; 
int rec[110];
int state[70],top;
int dp[110][70][70];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<(1<<m);i++){
		if((i&(i<<1)||(i&(i<<2))))continue; 
		int k=i;
		while(k){
			++num[top];
			k&=(k-1);                 //这一步是去掉k二进制下的一个1
		}
		state[top++]=i;
	}
	for(int i=0;i<n;i++){
		cin>>s[i];
		for(int j=0;j<m;j++)
			if(s[i][j]=='H')
		 	rec[i]|=(1<<j); 
	}
	for(int i=0;i<top;i++) {
		if(state[i]&rec[0])continue;
		dp[0][0][i]=num[i];
	}
	for(int i=1;i<n;i++){
		for(int j=0;j<top;j++) {
			if(state[j]&rec[i])continue; 
			for(int k=0;k<top;k++) {
				if(state[j]&state[k])continue;
				for(int t=0;t<top;t++) {
					if(state[j]&state[t])continue; 
					if(state[k]&state[t])continue;
					dp[i][k][j]=max(dp[i][k][j],dp[i-1][t][k]+num[j]); 
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<n;i++)
		for(int j=0;j<top;j++)
			for(int k=0;k<top;k++)
				ans=max(ans,dp[i][j][k]);
	printf("%d",ans);
}

[蓝桥杯 2019 省 A] 糖果

糖果店的老板一共有 M M M 种口味的糖果出售。为了方便描述,我们将 M M M 种口味编号 1 1 1 M M M

小明希望能品尝到所有口味的糖果。遗憾的是老板并不单独出售糖果,而是 K K K 颗一包整包出售。

幸好糖果包装上注明了其中 K K K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。

给定 N N N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。

输入格式

第一行包含三个整数 N N N M M M K K K

接下来 N N N 行每行 K K K 这整数 T 1 , T 2 , ⋯   , T K T_1,T_2, \cdots ,T_K T1,T2,,TK,代表一包糖果的口味。

输出格式

一个整数表示答案。如果小明无法品尝所有口味,输出 − 1 -1 1

样例输入 #1

6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2

样例输出 #1

2

提示

对于 30 % 30\% 30% 的评测用例, 1 ≤ N ≤ 20 1 \le N \le 20 1N20

对于所有评测样例, 1 ≤ N ≤ 100 1 \le N \le 100 1N100 1 ≤ M ≤ 20 1 \le M \le 20 1M20 1 ≤ K ≤ 20 1 \le K \le 20 1K20 1 ≤ T i ≤ M 1 \le T_i \le M 1TiM

蓝桥杯 2019 年省赛 A 组 I 题。

思路

  • 看到这道题,能想到是状压dp,因为 M , K M,K M,K的值都挺小的,算是一种暗示吧。但是一直弄不清楚状态应该设置为什么,状态转移应该从什么样的状态转移到什么样的状态
  • 看了别人的思路:
    • 状态压缩,把每一包糖果看成一个集合,把这个集合用一个整数表示。
    • 这个整数的二进制形式的第i位为1表示这包糖果里有第i种糖果
    • 最多一共有2^20 = 1048576种状态
    • 用已有的状态去更新加上第i包糖果后的状态

题解

#include <bits/stdc++.h>
using namespace std;
int n, m, k, x;
int a[400];
int dp[1 << 21];
int main(){
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= k; j++){ 
			cin >> x;
			a[i] |= 1 << (x - 1);     // 以二进制形式表示这包糖果中的每颗糖果
		}
	}
	memset(dp, 0x3f, sizeof(dp)); 
	dp[0] = 0; 
	for (int i = 1; i <= n; i++){
		for (int j = 0; j < (1 << m); j++){  
			if (dp[j] > n) continue;
			dp[j | a[i]] = min(dp[j | a[i]], dp[j] + 1);
		}
	}
	if (dp[(1 << m) - 1] > n) cout << -1;   
	else cout << dp[(1 << m) - 1];       
}

[蓝桥杯 2022 省 B] 李白打酒加强版

话说大诗人李白,一生好饮。幸好他从不开车。

一天,他提着酒壶,从家里出来,酒壶中有酒 2 2 2 斗。他边走边唱:

无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。

这一路上,他一共遇到店 N N N 次,遇到花 M M M 次。已知最后一次遇到的是花,他正好把酒喝光了。

请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?

注意:壶里没酒( 0 0 0 斗)时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。

输入格式

第一行包含两个整数 N N N M M M

输出格式

输出一个整数表示答案。由于答案可能很大,输出模 1000000007 1000000007 1000000007(即 1 0 9 + 7 10^9+7 109+7)的结果。

样例输入 #1

5 10

样例输出 #1

14

提示

【样例说明】

如果我们用 0 代表遇到花,1 代表遇到店, 14 14 14 种顺序如下:

010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100

【评测用例规模与约定】

对于 40 % 40 \% 40% 的评测用例: 1 ≤ N , M ≤ 10 1 \leq N, M \leq 10 1N,M10

对于 100 % 100 \% 100% 的评测用例: 1 ≤ N , M ≤ 100 1 \leq N, M \leq 100 1N,M100

蓝桥杯 2022 省赛 B 组 I 题。

思路

  • 思路就像经典的李白打酒一样,状压,然后枚举相应状态是否符合条件。但是,这样最多就只能过40%数据,因为要开 2 n + m 2^{n+m} 2n+m,按照传统的方法来,肯定MLE
  • 看了别人的思路:不用状态压缩,因为没办法压缩
    • 首先我们有必要对酒的奇偶性进行讨论,因为当走到第i个位置时酒的数量为奇,则第i个位置不可能为店,因为无论是奇数还是偶数,乘以2得到的数都是一个偶数,所以只有当k是偶数时第i个位置才可能是店
    • 假如第i个位置是店,那么这个时候在第i-1个位置的酒的数量就是k/2,而遇到花的数量跟第i-1个位置是一样的都是j,这个很好理解
    • 下面再看一下第i个位置是花的情况,那么第i-1个位置的酒的数量一定是k+1,因为在第i个位置消耗了1,而到达第i个位置遇到的花的数量就要比第i-1个位置遇到花的数量多1,因为我们现在是在假设第i个位置是花

题解

#include<bits/stdc++.h>
using namespace std;
const int N=113;
const int mod=1e9+7;
long long f[N*2][N][N];//f[i][j][k]表示走到了第i个位置,遇到了j个花,还剩 k斗酒的合法方案数 
int main(){
	f[0][0][2]=1;
	int n,m;
	cin>>n>>m;
	for(int i=1;i<n+m;i++)
	for(int j=0;j<m;j++)
	for(int k=0;k<=m;k++)//酒的数量不能超过花的数量,否则就算之后一直是花也喝不完 
	{
		if(!(k&1))//k是偶数,则第i个位置可以是店,否则不可以是店
			f[i][j][k]=(f[i][j][k]+f[i-1][j][k>>1])%mod;
		if(j>=1)//无论k是奇数还是偶数,第i个位置都可以是花 
			f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k+1])%mod;
	}
	printf("%lld",f[n+m-1][m-1][1]);
}

总结

  • 首先看到动态规划的题目中有数据量 ≤ 20 ≤20 20的情况下,很有可能用到状态压缩
  • 先进行状态压缩,然后再考虑动态规划的思路

有关动态规划算法刷题笔记【状压dp】的更多相关文章

  1. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. ruby - 在 Ruby 中动态创建数组 - 2

    有没有办法在Ruby中动态创建数组?例如,假设我想遍历用户输入的书籍数组:books=gets.chomp用户输入:"TheGreatGatsby,CrimeandPunishment,Dracula,Fahrenheit451,PrideandPrejudice,SenseandSensibility,Slaughterhouse-Five,TheAdventuresofHuckleberryFinn"我把它变成一个数组:books_array=books.split(",")现在,对于用户输入的每一本书,我想用Ruby创建一个数组。伪代码来做到这一点:x=0books_array.

  4. ruby - 是否可以将 IRB 提示配置为动态更改? - 2

    我想在IRB中浏览文件系统并让提示更改以反射(reflect)当前工作目录,但我不知道如何在每个命令后进行提示更新。最终,我想在日常工作中更多地使用IRB,让bash溜走。我在我的.irbrc中试过这个:require'fileutils'includeFileUtilsIRB.conf[:PROMPT][:CUSTOM]={:PROMPT_N=>"\e[1m:\e[m",:PROMPT_I=>"\e[1m#{pwd}>\e[m",:PROMPT_S=>"FOO",:PROMPT_C=>"\e[1m#{pwd}>\e[m",:RETURN=>""}IRB.conf[:PROMPT_MO

  5. ruby-on-rails - carrierwave:在序列化动态属性上安装 uploader - 2

    首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟

  6. ruby - 在 Ruby 中动态生成多维数组 - 2

    我正在尝试动态构建一个多维数组。我想要的基本上是这样的(为简单起见写出来):b=0test=[[]]test[b]这给了我错误:NoMethodError:undefinedmethod`test=[[],[],[]]而且它工作正常,但在我的实际使用中,我不会事先知道需要多少个数组。有一个更好的方法吗?谢谢 最佳答案 不需要像您正在使用的索引变量。只需将每个数组附加到您的test数组:irb>test=[]=>[]irb>test[["a","b","c"]]irb>test[["a","b","c"],["d","e","f"]]

  7. ruby-on-rails - 使用 gmaps4rails 动态加载谷歌地图标记 - 2

    如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail

  8. ruby - 动态方法链? - 2

    如何在对象上调用方法名称的嵌套哈希?例如,给定以下哈希:hash={:a=>{:b=>{:c=>:d}}}我想创建一个方法,给定上面的散列,执行以下操作:object.send(:a).send(:b).send(:c).send(:d)我的想法是我需要从一个未知的关联中获取一个特定的属性(这个方法不知道,但程序员知道)。我希望能够指定一个方法链来以嵌套哈希的形式检索该属性。例如:hash={:manufacturer=>{:addresses=>{:first=>:postal_code}}}car.execute_method_hash(hash)=>90210

  9. ruby - 如何使用 method_missing 动态声明方法? - 2

    我有一个ruby​​程序,我想接受用户创建的方法,并使用该名称创建一个新方法。我试过这个:defmethod_missing(meth,*args,&block)name=meth.to_sclass我收到以下错误:`define_method':interningemptystring(ArgumentError)in'method_missing'有什么想法吗?谢谢。编辑:我以不同的方式让它工作,但我仍然很好奇如何以这种方式做到这一点。这是我的代码:defmethod_missing(meth,*args,&block)Adder.class_evaldodefine_method

  10. ruby - 动态扩展现有方法或覆盖 ruby​​ 中的发送方法 - 2

    假设我们有A、B、C类。Adefself.inherited(sub)#metaprogramminggoeshere#takeclassthathasjustinheritedclassA#andforfooclassesinjectprepare_foo()as#firstlineofmethodthenrunrestofthecodeenddefprepare_foo#=>prepare_foo()neededhere#somecodeendendBprepare_foo()neededhere#somecodeendend如您所见,我正在尝试将foo_prepare()调用注入

随机推荐