【题解】校内测10 简单DP

时间:2020-05-25
本文章向大家介绍【题解】校内测10 简单DP,主要包括【题解】校内测10 简单DP使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

T134291 T1.抄近路

题目背景

【题目描述】抄近路(shortline.cpp/c/pas)

题目描述

张琪曼和李旭琳每天要从家到车站,小区被道路分成许多正方形的块,共有N×M块。由于道路太多以及雾霾的影响,她们总是迷路,所以你需要帮她们计算一下从家到车站的最短距离。注意,一般情况下,小区内的方块建有房屋,只能沿着附近的街道行走,有时方块表示公园,那么就可以直接穿过。

输入格式

第一行是N和M(0<N,M≤1000)。注意,李旭琳家坐标在方块(1,1)的西南角,车站在方块(M,N)的东北角。每个方块边长100米。接下来一行是整数K,表示可以对角线穿过的方块坐标,然后有K行,每行是一个坐标。

输出格式

输出最短距离,四舍五入到整数米。

输入输出样例

输入 #1

3 2
3
1 1
3 2
1 2

输出 #1

383

分析

我这个题DP的东西是对角线的个数,f [n] [m] 表示从(1,1)走到(n,m) 能够路过的(把这个题放在平面直角坐标系里,固定路线是向右向上)最多的 对角线个数。当然肯定不会有走过去之后再回来(就是向左向下)走对角线这样的骚操作,因为这样并不会抄近路,而是绕远路。

本来应该乖乖的走(n+m)个100m的路,我们得到f[n] [m]之后,就会有2*f[n] [m] 个100m变成了 f[n] [m]个sqrt(2) * 100 m,这就是最终答案了。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>

using namespace std;

int n,m,k,a,b;
int G[1010][1010];
int f[1010],ans;
int main()
{
	cin >> n >> m >> k;
	for(int i=1;i<=k;i++){
		cin >> a >> b;
		G[a][b]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=m;j>=1;j--){
			f[j]=max(f[j],f[j-1]);
			if(G[i][j])	f[j]+=1;
		}
	}
//	cout<<f[m]<<endl;
	ans=(100*(m+n-2*f[m])+sqrt(20000.0)*f[m])+0.5;
	cout<<ans<<endl;
	return 0;
}

T134292 T1.简单背包问题2

题目背景

【洛谷赛制:不要写freopen( ) 】

【题目描述】简单背包问题2(pack2.cpp/c/pas)

题目描述

张琪曼:“为什么背包一定要完全装满呢?尽可能多装不就行了吗?” 李旭琳:“你说得对!” 现在的问题是,她们有一个背包容量为v(正整数,0≤v≤20000),同时有n个魔法石(0≤n≤30),每个魔法石有一个体积 (正整数)。要求从n个魔法石中,任取若干个装入包内,使背包的剩余空间为最小。

输入格式

第一行为一个整数,表示背包容量,第二行为一个整数,表示有n个魔法石,接下来n行,分别表示这n个魔法石的各自体积。

输出格式

只有一个整数,表示背包剩余空间。

输入输出样例

输入 #1

24     
6      
8      
3
12
7
9
7

输出 #1

0

这个题等价于这个题:一本通1295:装箱问题

这个博客中记录了有关装箱问题的一维循环和二维循环写法以及初学者(就是我这样的菜菜)不太理解的几个问题。

摘抄如下:

1295:装箱问题

状压如下,分析复杂度:n<=30,O((1<<30 ) * 30)=O(2e9 *30)= 6e10

肯定会T的!

for(int i=0;i<=(1<<n);i++){//2e9
		int ans0=0;int t=i;
		int num=n;while(t && num){//30
			if(t&1)	ans0+=ti[num];
			t/=2;
			num--;
		}
		if(ans0 <= v && ans0>ans)	ans=ans0;
	}
	ans=v-ans;
	cout << ans <<endl;

二位数组如下

cin>>v>>n;
	for(int i=1;i<=n;i++)	cin>>ti[i];
	for(int i=1;i<=n;i++){
		for(int j=v;j>=ti[i];j--){
			if(f[i-1][j-ti[i]]+ti[i] <= v)
			f[i][j]=max(f[i][j],f[i-1][j-ti[i]]+ti[i]);
		}
	}
	for(int i=1;i<=n;i++)	ans=max(ans,f[i][v]);
	ans=v-ans;
	cout<<ans<<endl;

然鹅错了

滚动数组如下

cin>>v>>n;
for(int i=1;i<=n;i++)	cin>>ti[i];
for(int i=1;i<=n;i++){
	for(int j=v;j>=ti[i];j--){
		//if(f[j-ti[i]]+ti[i] <= v)
		f[j]=max(f[j],f[j-ti[i]]+ti[i]);

		if(f[j] > ans && f[j] < v)	ans=f[j];
	}
}
//ans=f[v];
ans=v-ans;
cout<<ans<<endl;

是错的,错误答案和二位数组一样

另一个如下

cin>>v>>n;
for(int i=1;i<=n;i++)	cin>>ti[i];
for(int i=1;i<=n;i++){
	for(int j=v;j>=ti[i];j--){
		if(f[j-ti[i]]+ti[i] <= v)
		f[j]=max(f[j],f[j-ti[i]]+ti[i]);
		//if(f[j] > ans && f[j] < v)	ans=f[j];
	}
}
ans=f[v];
ans=v-ans;
cout<<ans<<endl;

这便对了。是1维的,不记录无用状态的一维的。

对于1、2维,我是这么想的:如果是2维,意味着第i个物品只能使用前i个物品的状态,

如果是1维,第i 个物品还能使用同样是第i 个物品,但是体积更小的状态。但是这样可能会出现第i个物品选两次的情况不是吗?但是如果倒着枚举就不会了(hh我就是倒着枚举的)

哦,好像应该这么解释:如果是一维的,那么第i 个物品相当于使用的是前1~i-1的所有物品的状态,所以会更优。

但是根据这个,我们可以给二维的程序加一句:

f[i][j]=max(f[i][j],f[i-1][j]);

便可达到同一维数组一样的效果。

然鹅还不对

经过调试,我发现了 问题所在:

1.j应该枚举到0,而不是枚举到ti[i] ,这样才能正确的顺延

2.其实if(f[j-ti[i]]+ti[i] <= v) 这一句是没用的,因为j的范围和f [i] [j]的范围是一样的,既然j<v,f [i] [j] 也就不会大于v了

最终的二维代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>

using namespace std;


int v,n,ti[33];
int ans;
int f[33][20010];
int main()
{
	
	cin>>v>>n;
	for(int i=1;i<=n;i++)	cin>>ti[i];
	for(int i=1;i<=n;i++){
		for(int j=v;j>=0;j--){
			f[i][j]=max(f[i][j],f[i-1][j]);
			if(j >= ti[i])
				f[i][j]=max(f[i][j],f[i-1][j-ti[i]]+ti[i]);
		}
	}
	ans=f[n][v];
	ans=v-ans;
	cout<<ans<<endl;
	
	
	return 0;
}

T134293 T2.货币系统问题

题目背景

【题目描述】货币系统问题(money.cpp/c/pas)

题目描述

货币是在国家或经济体内的物资与服务交换中充当等价物,或是偿还债务的特殊商品,是用作交易媒介、储藏价值和记账单位的一种工具。魔法世界的货币的历史,可以追溯至史前以物易物的阶段,后来经过金属货币、金银、纸币以及金银本位制度,演化至现代的货币体系,现已知魔法世界的货币系统有V种面值,求组成面值为N的货币有多少种方案。

输入格式

第一行为两个整数V和N,V是货币种类数目,1≤V≤25,N是要构造的面值,1≤N≤1000。 第二行为V种货币的面值。

输出格式

输出方案数。

输入输出样例

输入 #1

3 10
1 2 5

输出 #1

10

分析

这是一个裸的完全背包问题。DP的内容是方案数。3层循环。一层货币种类(物品),一层构造面值(背包容积),一层每种货币的数量。3层循环可以变成两维的,只要改变一下第二层的循环顺序即可。由于我是从01背包的基础上写的代码,所以第二层循环倒着枚举,再加一层循环枚举个数以达到目的。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>

using namespace std;

int n, m, a[1010];
long long f[1010][10010];


int main()
{
	cin>>n>>m;
	if(n==0){
			cout<<0<<endl;exit(0);//n=0的话后面就没法进行了,直接cout0 
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	//初始化,前0种货币组成面值0的方案数是1 
	f[0][0]=1;
	
	for(int i=1;i<=n;i++){//货币种类 
		for(int j=0;j<=m;j++){//目标面值 
			for(int k=0;k*a[i]<=j;k++)//个数 
			if(j-a[i]*k >= 0){
				f[i][j]+=f[i-1][j-a[i]*k];
			}
		}
	}
	cout<<f[n][m];//答案即为f[n][m] 
	return 0;
}

T134294 T3.收益

题目背景

收益(Profit.cpp/c/pas)简单来说,就是你有一笔钱,你要将这笔钱去投资债券,现在有d种债券,每种债券都有一个价值和年收益,债券的价值是1000的倍数,问你如何投资在n年后的获得最大收益。

题目描述

输入格式

第一个为一个整数M,表示有M组数据。 每组数据第一行有两个整数,表示初始资金(不超过50000)和年数n。 每组数据第二行为一个整数d(1 ≤ d≤10),表示债券种类。 随后d行每行有两个整数,表示该债券的价值和年收益。年收益不会超过债券价值的10%。 所有数据不超过整型取值范围。

输出格式

每组数据,输出n年后获得的最大收益。

输入输出样例

输入 #1

1
10000 4
2
4000 400
3000 250

输出 #1

14050

读这个题,他买债券是一年一年的买,并不是说今年买了之后这个资产就固定在这个债券上了,而是今年赚了钱之后收回投资的本金和赚的钱,然后开始新一年的投资。

所以这个题是在普通的完全背包的3层循环外面加一层年份,一共3层循环即可解决。不过不知道年份的数值范围(应该不会太大)。

再考虑最优子结构。既然投资是以年为单位进行,那我今年赚的越多,下一年才可能赚的更多。额,事实上这并不能够叫做最优子结构,因为每一年的投资都是独立的,并不是由上一年转移来的。所以,这个操作应该叫他贪心。

还有一件事,既然题目中说债券的价格是1000的整数倍,那我们DP的时候直接一千一千的DP就行。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>

using namespace std;

int mon/*money*/,yea/*year*/,d,w[11]/*价格*/,v[11]/*收益*/;//题目中的变量 
long long ans/*用于存储投资收益,也可以不用*/;
long long f[50010];//DP标准数组 
int main()
{
	int T;//组数 
	cin >> T;
	while(T--){
		//以下为输入 
		cin >> mon >> yea >> d;
		for(int i=1;i<=d;i++)	cin>>w[i]>>v[i];
		//以下为DP 
		for(int i=1;i<=yea;i++){
			//每一年都要初始化 
			ans=0;
			memset(f,0,sizeof(f));
			//以下为完全背包标准操作 
			for(int j=1;j<=d;j++){//物体 
				for(int k=mon/1000*1000;k>=0;k-=1000){//体积 
					for(int num=0;num<=mon/w[j];num++)//个数 
					//以上两维循环可以改为这一条:
					//for(int k=0;k<=mon;k+=1000)  意思就是说正着枚举,不用手动考虑个数 
						if(k>=w[j]*num)
							f[k]=max(f[k],f[k-w[j]*num]+v[j]*num);//转移 
						ans=max(ans,f[k]);//事实上,ans的数值一定等于f[mon/1000*1000],那我为什么还要多这一步操作?? 
				}
			}
			mon+=ans;//挣到钱了,算进我的总资产 
//			cout<<mon<<endl;
		}
		cout<<mon<<endl; //输出 
	}
	return 0;
}

原文地址:https://www.cnblogs.com/ZhengkunJia/p/12958048.html