算法基础精选题单 动态规划(dp)(背包问题)(个人题解)

news2024/11/8 6:30:41

目录

前言:

正文:

  题单:【237题】算法基础精选题单_ACM竞赛_ACM/CSP/ICPC/CCPC/比赛经验/题解/资讯_牛客竞赛OJ_牛客网 (nowcoder.com)

01背包:

NC16693 装箱问题:

NC16650 [NOIP2005]采药:

NC16666 [NOIP2006]开心的金明:

NC19158 失衡天平:

NC231128 Steadily Growing Steam:

完全背包:

NC21467 [NOIP2018]货币系统:

多重背包:

NC235950 多重背包:

分组背包:

NC16671 [NOIP2006]金明的预算方案:

二位费用背包:

NC14699 队伍配置:

map优化超大背包:

NC235951 草药大师:

后记:


前言:

   背包问题是很经典的dp问题,从最简单的01背包问题衍生了很多其他更复杂的形式,在这我不详细介绍背包问题的动态转移公式的推导,感兴趣的可以在各个平台搜搜看,他们讲的肯定比我好。

正文:

  题单【237题】算法基础精选题单_ACM竞赛_ACM/CSP/ICPC/CCPC/比赛经验/题解/资讯_牛客竞赛OJ_牛客网 (nowcoder.com)

01背包:

NC16693 装箱问题:

#include <bits/stdc++.h>
using namespace std;
int a[35];
int dp[200005];
int main(){
	int v,n;
	cin>>v>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=v;j>=a[i];j--){
			dp[j]=max(dp[j-a[i]]+a[i],dp[j]);
		}
	}
	cout<<v-dp[v];
	return 0;
}

NC16650 [NOIP2005]采药:

#include <bits/stdc++.h>
using namespace std;
int dp[100000];
int w[1005],v[1005];
int main(){
	int t,n;
	cin>>t>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=t;j>=w[i];j--){
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		}
	}
	cout<<dp[t];
	return 0;
}

NC16666 [NOIP2006]开心的金明:

#include <bits/stdc++.h>
using namespace std;
int dp[30050];
int a[105],p[105];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)cin>>a[i]>>p[i];
	for(int i=1;i<=m;i++){
		for(int j=n;j>=a[i];j--){
			dp[j]=max(dp[j-a[i]]+a[i]*p[i],dp[j]);
		}
	}
	cout<<dp[n]<<endl;
	return 0;
}

上面三道题基本上就是模板题,唯一要注意的就是第三题他物品的价值是价格乘重要度。

NC19158 失衡天平:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int w[105];
int dp[105][N];
int main(){
	int n,m,ans=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	memset(dp,-0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=300;j++){
			dp[i][j]=dp[i-1][j];
			dp[i][j]=max(dp[i][j],max(dp[i-1][abs(j-w[i])]+w[i],dp[i-1][j+w[i]]+w[i]));
			//cout<<i<<" "<<j<<" "<<dp[i][j]<<endl;
		}
	}
	for(int i=0;i<=m;i++){
		ans=max(dp[n][i],ans);
		//cout<<dp[n][i]<<endl;
	}
	cout<<ans<<endl;
	return 0;
}

其实觉得这道题不像01背包,可能是取物品的思想比较类似吧,这里dp[i][j]的状态表示为取到第i个物品,天平差值为j时物品重量最大值,这个状态可以由以状态转移过来:

  • 不拿第i个物品。
  • 拿第i个物品并放在天平物品重量小的那一边
  • 拿第i个物品并放在天平物品重量大的那一边

所以状态转移方程为(先事先把第一种情况转移)dp[i][j]=max(dp[i][j],max(dp[i-1][abs(j-w[i])]+w[i],dp[i-1][j+w[i]]+w[i]));

这边要注意我们这种转移方式会引用到一些非法值(不可能转移到的值),所以我们要将处dp[0][0]以外的值都初始化为负无穷,以免将非法值转移过来。

最后枚举小于m的差值找出最大答案。

NC231128 Steadily Growing Steam:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[105],v[105],dp[105][105][1350];
int main(){
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>v[i]>>a[i];
	}
	memset(dp,-0x3f,sizeof(dp));
	dp[0][0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=k;j++){
			for(int k=0;k<=1350;k++){
				dp[i][j][k]=dp[i-1][j][k];
				dp[i][j][k]=max(dp[i][j][k],max(dp[i-1][j][abs(k-a[i])]+v[i],dp[i-1][j][abs(k+a[i])]+v[i]));
				if(j!=0)dp[i][j][k]=max(dp[i][j][k],max(dp[i-1][j-1][abs(k-a[i]*2)]+v[i],dp[i-1][j-1][abs(k+a[i]*2)]+v[i]));
				//cout<<i<<" "<<j<<" "<<k<<" "<<dp[i][j][k]<<endl;
			}
		}
	}
	ll ans=0;
	for(int i=0;i<=k;i++){
		ans=max(ans,dp[n][i][0]);
	}
	cout<<ans<<endl;
	return 0;
}

上题的hard版,加上了k次翻倍的能力,并且这次要求两边完全相等,所以我们的转移方程就比上一题要有点不同了:

  • 不选当前的卡片:dp[i][j][k] = dp[i-1][i][j];
  • 选当前的卡片但将其放到点数大的那一个里面(不使用技能):dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][k+a[i].point]+a[i].v)
  • 选当前的卡片但将其放到点数小的那一个里面(不使用技能):dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][abs(k-a[i].point)]+a[i].v)
  • 选当前的卡片但将其放到点数大的那一个里面(使用技能):dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k+a[i].point]+a[i].v)
  • 选当前的卡片但将其放到点数小的那一个里面(使用技能):dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][abs(k-a[i].point)]+a[i].v)

最后枚举翻倍次数到k,找到最大的卡牌值。

完全背包:

NC21467 [NOIP2018]货币系统:

#include<bits/stdc++.h>
using namespace std;
int a[105],dp[25005];
int main(){
	int t;
	cin>>t;
	while(t--){
		int n;
		cin>>n;
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		sort(a+1,a+n+1);
		dp[0]=1;int ans=n;
		for(int i=1;i<=n;i++){
			if(dp[a[i]]==1){
				ans--;
				continue;
			}
			else{
				for(int j=a[i];j<=a[n];j++){
					dp[j]=dp[j]||dp[j-a[i]];
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

理解题意我们很容易知道一种货币如果可以由其他货币表示那么他就是没有存在必要的,所以我们先将货币按大小排序,从小到大进行多重背包,如果这个货币能被之前的货币表示就标记并让答案-1(答案初值设为n)。

多重背包:

NC235950 多重背包:

#include <bits/stdc++.h>
using namespace std;
const int N = 11010, M = 2010;
int w[N], v[N];
int dp[M];
int main(){
    ios::sync_with_stdio(false);
    int n,m;
    cin>>n>>m;
    int cnt=0;
    while(n--){
        int b,a,s;//a是重量,b是价值,s是数量
        cin>>s>>a>>b;
        for(int k=1;k<=s;k*=2){
            cnt++;
            w[cnt]=a*k,v[cnt]=b*k;
            s-=k;
        }
        if(s>0){
            cnt++;
            w[cnt]=a*s,v[cnt]=b*s;
        }
    }
    n=cnt;
    for(int i=1;i<=n;i++)
        for(int j=m;j>=w[i];j--)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    cout<<dp[m]<<"\n";
    return 0;
}

多重背包模板。

分组背包:

NC16671 [NOIP2006]金明的预算方案:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef struct stu{
	int v;
	int p;
}node;
node a[65];
vector<node> aa[65];
int dp[32005];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int v,p,q;
		cin>>v>>p>>q;
		p=p*v;
		if(q==0){
			a[i].v=v;
			a[i].p=p;
		}
		else{
			aa[q].push_back({v,p});
		}
	}
	for(int i=1;i<=m;i++){
		if(!a[i].v)continue;
		for(int j=n;j>=0;j--){
			for(int l=0;l<=3;l++){
				int v=a[i].v;int p=a[i].p;
				for(int k=0;k<aa[i].size();k++){
					if((l>>k)&1){
						v+=aa[i][k].v;
						p+=aa[i][k].p;
					}
				}
				if(j>=v)dp[j]=max(dp[j],dp[j-v]+p);
			}
			//cout<<dp[j]<<endl;
		}
	}
	cout<<dp[n]<<endl;
	return 0;
}
 

因为要买附件必须有主件,并且一个主件最多带有两个附件,那么状态最多就由五个状态表示出来:

  • 不选,然后去考虑下一个
  • 选且只选这个主件
  • 选这个主件,并且选附件1
  • 选这个主件,并且选附件2
  • 选这个主件,并且选附件1和附件2。

所以最后我们用vector处理附件后就直接开始枚举主件,每个主件可以派生最多五种选择,这不就是多重背包模型吗,最后可以通过二进制操作来枚举这五种情况,然后通过转移方程来求最大值。

二位费用背包:

NC14699 队伍配置:

#include<bits/stdc++.h>
using namespace std;
long long dp[200][10][10];
int main(){
	long long n,m,d,ans=0;
	cin>>n>>m>>d;
	memset(dp,-0x3f3f3f,sizeof(dp));
	dp[0][0][0]=0;
	for(int i=1;i<=n;i++){
		int a,b;
		cin>>a>>b;
		for(int j=d;j>=b;j--){
			for(int k=1;k<=5;k++){
				dp[j][k][0]=max(dp[j][k][0],dp[j-b][k-1][0]+a);
				ans=max(ans,dp[j][k][0]);
			}
		}
	}
	for(int i=1;i<=m;i++){
		int a,b;
		cin>>a>>b;
		for(int j=d;j>=b;j--){
			for(int k=1;k<=5;k++){
				for(int l=1;l<=k;l++){
					dp[j][k][l]=max(dp[j][k][l],dp[j-b][k][l-1]+a);
					ans=max(ans,dp[j][k][l]);
				}
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

先枚举从者不带礼装的情况,之后再来枚举礼装的情况(礼装的内部分配对于最后的结果是不影响的)。状态dp[i][j][k]表示费用为i,从者数量为j,礼装数量为k时atk的最大值。两个状态转移易知为

dp[j][k][0]=max(dp[j][k][0],dp[j-b][k-1][0]+a)

dp[j][k][l]=max(dp[j][k][l],dp[j-b][k][l-1]+a)

最后从每一个状态中挑最大值即为答案。

map优化超大背包:

NC235951 草药大师:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll,ll> dp;
int main(){
	int n,s;
	cin>>n>>s;
	dp[0]=0;
	for(int i=1;i<=n;i++){
		int t,v;
		cin>>t>>v;
		for(map<ll,ll>::reverse_iterator it=dp.rbegin();it!=dp.rend();it++){
			if((it->first)+t<=s){
				dp[it->first+t]=max(dp[it->first+t],dp[it->first]+v);
				//cout<<dp[it->first+t]<<endl;
				//cout<<i<<" "<<it->first+t<<" "<<dp[it->first+t]<<endl;
				
			}
			//cout<<i<<" "<<it->first+t<<" "<<dp[it->first+t]<<endl;
		}
	}
	ll ans=0;
	for(map<ll,ll>::iterator it=dp.begin();it!=dp.end();it++){
		
		ans=max(ans,it->second);
	}
	cout<<ans<<endl;
	return 0;
}

这种背包没见过,所以我直接奔着题解去了,其实就是在普通的01背包中优化第二层循环,原先我们时一步一步加一枚举过去的,用map的话就可以只枚举已经出现过的部分,大大优化了空间和时间,最终map中的数据就为所有可能会出现的数据。

值得一提的是在我本地的编译器上运行这串代码的结果竟然和牛客上不一样?发给我同学在他们电脑上跑的结果与我的还不一样?不知道是不是我的编译器版本太老了。

后记:

  这下简单的dp问题就都解决了,剩下的树形dp和状压dp估计够我写的了(。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1861801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode刷题之HOT100之最小栈

听歌&#xff0c;做题&#xff01; 1、题目描述 2、逻辑分析 拿到题目一脸懵&#xff0c;有点看不懂啥意思&#xff0c;看了题解才知道啥意思。要实现在常数时间内检索到最小元素的栈&#xff0c;需要使用一个辅助栈来每次存入最小值。 使用Deque作为栈的实现是因为它提供了…

【Linux】进程信号_1

文章目录 八、进程信号1.信号 未完待续 八、进程信号 1.信号 信号和信号量之间没有任何关系。信号是Linux系统提供的让用户/进程给其他进程发送异步信息的一种方式。 常见信号&#xff1a; 当信号产生时&#xff0c;可选的处理方式有三种&#xff1a;①忽略此信号。②执行该…

C++ 14新特性个人总结

variable templates 变量模板。这个特性允许模板被用于定义变量&#xff0c;就像之前模板可以用于定义函数或类型一样。变量模板为模板编程带来了新的灵活性&#xff0c;特别是在定义泛化的常量和元编程时非常有用。 变量模板的基本语法 变量模板的声明遵循以下基本语法&am…

【Linux网络(一)初识计算机网络】

一、网络发展 1.发展背景 2.发展类型 二、网络协议 1.认识协议 2.协议分层 3.OSI七层模型 4.TCP/IP协议 三、网络传输 1.协议报头 2.局域网内的两台主机通信 3.跨网络的两台主机通信 四、网络地址 1.IP地址 2.MAC地址 一、网络发展 1.发展背景 计算机网络的发展…

ITIL发展之路:从v3到v4的演变与应用

在当今瞬息万变的技术环境中&#xff0c;IT服务管理&#xff08;ITSM&#xff09;已成为企业运营的关键支柱。ITIL&#xff08;Information Technology Infrastructure Library&#xff0c;信息技术基础设施库&#xff09;作为全球公认的ITSM最佳实践框架&#xff0c;帮助组织在…

AI大模型日报#0625:OpenAI停止不支持国家API、大模型「考上」一本、苹果上新视觉模型4M-21

导读&#xff1a;AI大模型日报&#xff0c;爬虫LLM自动生成&#xff0c;一文览尽每日AI大模型要点资讯&#xff01;目前采用“文心一言”&#xff08;ERNIE-4.0-8K-latest&#xff09;生成了今日要点以及每条资讯的摘要。欢迎阅读&#xff01;《AI大模型日报》今日要点&#xf…

打电话时可以变声的软件有吗?变声器免费的直接说话的那种,直播要用!

在直播盛行的当下&#xff0c;变声器成为许多主播增加趣味性和神秘感的重要工具。对于想要在直播中尝试不同声音效果的用户来说&#xff0c;寻找一款既方便又免费的变声器软件显得尤为重要。本文将详细介绍11款可以直接说话的免费变声器软件&#xff0c;助你在直播中大展身手。…

【内网穿透】FRP 跨平台内网穿透 支持windows linux x86_64 arm64 端口范围映射

AI提供的资料&#xff1a; FRP&#xff08;Fast Reverse Proxy&#xff09;是一个专为内网穿透设计的高性能反向代理程序。以下是一些关于FRP的详细资料&#xff0c;帮助您更好地理解和使用这一工具&#xff1a; 核心特点&#xff1a; 内网穿透&#xff1a;能够将位于内网的…

光伏开发有没有难点?如何解决?

随着全球对可再生能源的日益重视&#xff0c;光伏技术作为其中的佼佼者&#xff0c;已成为实现能源转型的关键手段。然而&#xff0c;光伏开发并非一帆风顺&#xff0c;其过程中也面临着诸多难点和挑战。本文将对这些难点进行探讨&#xff0c;并提出相应的解决策略。 一、光伏开…

Linux内核开发-编写一个proc文件

0.前言 上一章&#xff08;点击返回上一章&#xff09;完成了一个内核模块的编写&#xff0c;实现了在内核运行时的动态加载和卸载。 在模块的开发调测过程中或者模块运行过程中&#xff0c;可能需要打印内核模块的变量的值或者想要动态开关模块的运行日志打印&#xff0c;那么…

[word] Word如何删除所有的空行? #职场发展#学习方法

Word如何删除所有的空行&#xff1f; 很多网友从网页复制文字粘贴到word文档后发现段落之间有空行&#xff0c;如果文字不多&#xff0c;手动删除这些空行也没有多少工作量&#xff0c;但是如果文字的字数达到成千上万&#xff0c;一个个手动删除这些空行还是很繁琐的。那么&a…

礼让,不是一昧地退让,而是表达我们的素养、品德

礼 / 让&#xff0c;发心是文明相处&#xff0c;互助互让&#xff0c;是君子之交

鸿蒙北向开发 ubuntu20.04 gn + ninja环境傻瓜式搭建闭坑指南

ninja跟gn都是比较时髦的东西,由歪果仁维护,如果走下载源码并编译这种流程的话需要走github跟google官网下载,国内的用网环境相信各位傻瓜都知道,github跟google这几年基本是属于连不上的状态,好在你看的鸿蒙项目跟国内的一些软件大厂已经帮你爬过梯子了,ninja工具跟gn工具已经…

【应用开发一】LED开发

文章目录 1应用层控制外设的两种方式2 sysfs和/sys关系3 LED控制方式3.1 基本情况3.2 LED属性文件介绍3.3 命令行属性测试3.4 led程序3.5 开发板上测试 1应用层控制外设的两种方式 使用设备文件控制 在Linux系统下&#xff0c;一切皆是文件。应用层控制底层硬件同样也是通过文…

堆的实现详解

目录 1. 堆的概念和特点2. 堆的实现2.1 堆向下调整算法2.2堆的创建2.3 建堆时间复杂度2.4 堆的插入2.5 堆的删除2.6 堆的代码实现2.6.1 结构体2.6.2 初始化2.6.3 销毁2.6.4 插入2.6.5 删除2.6.6 获取堆顶2.6.7 判空2.6.8 个数2.6.9 向上调整2.6.10 向下调整3. 堆的实现测试测试…

SQL连接与筛选:解析left join on和where的区别及典型案例分析

文章目录 前言一、left join on和where条件的定义和作用left join on条件where条件 二、left join on和where条件的区别原理不同left join原理&#xff1a;where原理&#xff1a; 应用场景不同执行顺序不同&#xff08;作用阶段不同&#xff09;结果集不同 三、实际案例理解lef…

深入JVM:详解JIT即时编译器

文章目录 深入JVM&#xff1a;详解JIT即时编译器一、序言二、基础概念1、何为JIT即时编译2、热点代码 三、HotSpot内置的即时编译器1、C1编译器2、C2编译器3、分层编译3.1 协作流程 四、常见JIT优化技术1、方法内联2、逃逸分析&#xff08;1&#xff09;同步锁消除&#xff08;…

高考填报志愿,找准自己的真兴趣来选择专业

又是一年一度的高考填报志愿的时间了&#xff0c;毕业生们要根据自己的分数&#xff0c;在很短的时间内确定自己的专业。专业千万条&#xff0c;兴趣第一条。专业的选择很大程度上决定着大学的学习生活是否顺利&#xff0c;甚至决定着以后的职业生涯。在纷繁复杂的专业中&#…

全球首个数字人开源了

DUIX&#xff08;Dialogue User Interface System&#xff09;是硅基智能打造的AI数字人智能交互平台。通过将数字人交互能力开源&#xff0c;开发者可自行接入多方大模型、语音识别&#xff08;ASR&#xff09;、语音合成&#xff08;TTS&#xff09;能力&#xff0c;实现数字…

【AI落地应用实战】如何高效检索与阅读论文——302.AI学术论文工具评测

一、引言 作为一名学术领域的探索者&#xff0c;我们都知道&#xff0c;检索和阅读论文是我们获取知识、启发思考、验证假设的基石&#xff0c;也是日常学习中必不可少的基本功之一。然而在浩瀚的学术海洋中&#xff0c;如何快速、准确地找到我们需要的论文&#xff0c;就像是…