背包问题总结:01背包变形 rqnoj169 671 117 622 99 140

这个专题主要是总结一些01背包的变形问题,题目主要筛选来自RQNOJ(因为训练用的就是这个OJ啊),嗯话不多说进入专题。

例一: RQNOJ169 最小乘车费用 

题目描述

假设某条街上每一公里就有一个公共汽车站,并且乘车费用如下表:

公里数 1 2 3 4 5 6 7 8 9 10

费用 12 21 31 40 49 58 69 79 90 101

而任意一辆汽车从不行驶超过10公里。某人想行驶n公里,假设他可以任意次换车,请你帮他找到一种乘车方案,使得总费用最小

注意:10公里的费用比1公里小的情况是允许的。


转换一下题意,那么这道题的题意就是我有体积为1-10的各个物品,各个花费为val[1]-val[10]要求刚好放满体积为V的背包时的最小花费。

题意转换完这样之后,我们便可以像01背包一样设计状态了。dp[i]表示填满体积为i的包所花的最小价值。

接着考虑转移,dp[i]一定是从1-10公里前能转移到它的值+选择这个公里的价值 的最小值转移而来。

需要注意的是,考虑到每一个点i的时候,由于要考虑之前的最小值,那么需要先求出i之前的所有点的最小值,那么需要先枚举i再枚举1-10每一个长度。

下附AC代码。

#include
#include
#include
#include
#define maxn 105
using namespace std;
int n=10;
int v;
int val[maxn];
int dp[maxn];
int main()
{
    memset(dp,0x3f,sizeof(dp));
	
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&dp[i]);
		val[i]=dp[i];
	}
	scanf("%d",&v);
	
	for(int i=1;i<=v;i++)
	{
		for(int j=1;j<=10;j++)
		if(i-j>=1)
		{
			dp[i]=min(dp[i],dp[i-j]+val[j]);
		}
	}
	
	printf("%d\n",dp[v]);
}


例二: RQNOJ671 纯洁的买卖

题目描述

ALEJ并不是财迷,但是作为纯洁党的伟大领袖,不挣钱,纯洁的事业怎么能坚持下去!纯洁党现在已经有M(1<=M<=100000)元经费了。ALEJ有一个富II代朋友,叫做HSW,HSW特别喜欢高价收藏一些餐巾纸(毛泽东用过的)、袜子(恐龙穿过的)、马桶垫(还珠格格坐过的)、红领巾(毛新宇戴过的)等等,总之,没有他不买的。因此ALEJ想借HSW这个大财主,通过倒买倒卖拥有尽可能多的钱。

有N(1<=N<=100)件物品供他选择,每件物品的买入价为c[i](1<=c[i]<=100000)元,HSW的收藏价为r[i](1<=r[i]<=100000)元。每向HSW卖出一件物品i之后,还要向政府上交c[i]元的税。每种物品的数量都是无限的。

ALEJ想知道,通过一次买卖(种类、数量没有限制)后,纯洁党的经费能有多少。


首先分析题意:这道题呢首先发现每种物品是无限的,每个物品是c[i]的价格,可以获得r[i]-c[i](价格)-c[i](税)的价值。

那么好了,我们敏锐的双眼就会发现这是一道无限背包题目,体积为c[i],价值为r[i]-2*c[i]。

那么直接用无限背包的代码实现,实现部分没有什么特殊细节。不过最后需要注意,我们算出来的是最大能增加的价值,需要再加上本金。

下附AC代码。

#include
#include
#include 
#define maxn 105
using namespace std;
int n,m;
int c[maxn],w[maxn];
int dp[1000005];
int main()
{
    scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&c[i],&w[i]);
		for(int j=c[i];j<=m;j++)
		{
			dp[j]=max(dp[j],dp[j-c[i]]+w[i]-2*c[i]);
		}
	}
	printf("%d\n",dp[m]+m);
}


例三: RQNOJ117 最佳课题选择

题目描述

NaCN_JDavidQ要在下个月交给老师n篇论文,论文的内容可以从m个课题中选择。由于课题数有限,NaCN_JDavidQ不得不重复选择一些课题。完成不同课题的论文所花的时间不同。具体地说,对于某个课题i,若NaCN_JDavidQ计划一共写x篇论文,则完成该课题的论文总共需要花费Ai*x^Bi个单位时间(系数Ai和指数Bi均为正整数)。给定与每一个课题相对应的Ai和Bi的值,请帮助NaCN_JDavidQ计算出如何选择论文的课题使得他可以花费最少的时间完成这n篇论文。


首先理解题意,我们有n种东西,可以无限量选取共m个,使得加起来的总花费最小。

首先我们可以考虑设计状态,其实每一个物品可以选取的量也就是1-m个,那么就相当于,将每一种物品拆成了m个,每一个的体积为j (在1-m内),价格为Ai*j^Bi 。对于每一个拆分成的物品,那么就是唯一的大小了,类似于01背包。

接着可以考虑转移,对于一维普通的01背包,先枚举每一个物品,再枚举容量。这里需要多加一个数量k,但是这个k是放在第二个枚举呢,还是第三次枚举呢,看起来好像放在第二个枚举比较合理。

不过由于一维01背包的精髓就是状态转移与前一个的状态的数值互不干扰。我们来做一个假设,假设现在枚举到了i,如果我们选择了第二个枚举k,那么在每次枚举l的时候,同样体积j的dp值已经被更新了,而不是从i-1的dp值转移而来了。如果选择第三个枚举k,第二个枚举j,那么dp[j]仍然是从j的大到小更新,在同一次枚举i的时候,不会有多余的状态干扰。

下附AC代码。

#include
#include
#include
#include
#define maxn 100005
using namespace std;
long long n,m;
long long a[maxn],b[maxn];
long long dp[maxn];
long long mypow(long long n,long long k)
{
    long long ans=1;
	while(k)
	{
		ans*=n;
		k--;
	}
	return ans;
}
int main()
{
	cin>>n>>m;
	for(long long i=1;i<=m;i++)
	{
		cin>>a[i]>>b[i];
	}
	memset(dp,0x3f3f,sizeof(dp));
	dp[0]=0;
	
	for(long long i=1;i<=m;i++)
	{		
		for(long long j=n;j>=1;j--)
		{
			for(long long k=1;k<=n;k++)
			{
				if(j-k>=0 && dp[j-k]<=1234567)
				{
					dp[j]=min(dp[j],dp[j-k]+(a[i]*mypow(k,b[i])));
				}
			}
		}
	}
	cout<

例四: RQNOJ622 最佳课题选择

题目描述

设某一机器由 n个部件组成,每一种部件都可以从m个不同的供应商处购得。设W[i][j]是从供应商j处购得的部件i的重量,C[i][j] 是相应的价格。

试设计一个算法,给出总价格不超过d的最小重量机器设计。

【编程任务】

对于给定的机器部件重量和机器部件价格, 编程计算总价格不超过d的最小重量机器设计。


题意描述很清楚了。

首先我们来设计状态。由于对于每一个部件,必须从其中一家选一个,那么就是一个分组背包。

如果我们用dp[i][j]表示考虑到了前i个,价值不超过为d的最小重量。那么可以通过枚举这个选哪一家,来转移到下一家。

需要注意的是,由于必须选一个,所以我们的状态转移方程为dp[i+1][j+val[i][k]]=min(dp[i+1][j+val[i][k]],dp[i][j]+wei[i][k]); 从本身或者选转移而来。

下附AC代码。

#include
#include
#include
#include
#define maxn 1005
using namespace std;
int n,m,v;
int val[maxn][maxn];
int wei[maxn][maxn];
int dp[maxn][maxn];
int main()
{
    scanf("%d%d%d",&n,&m,&v);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		scanf("%d",&val[i][j]);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		scanf("%d",&wei[i][j]);
	}
	memset(dp,0x3f,sizeof(dp));
	dp[1][0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=v;j++)
		{
			for(int k=1;k<=m;k++)
			if(dp[i][j]<=12345678)
			{
				if(j+val[i][k]<=v)
				dp[i+1][j+val[i][k]]=min(dp[i+1][j+val[i][k]],dp[i][j]+wei[i][k]);
				
			}
		}
	}

	int ans=987654321;
	for(int i=0;i<=v;i++)
	ans=min(ans,dp[n+1][i]);
	printf("%d\n",ans);
}

例五: RQNOJ99 配置魔药

 题目描述

在《Harry Potter and the Chamber of Secrets》中,Ron的魔杖因为坐他老爸的Flying Car撞到了打人柳,不幸被打断了,从此之后,他的魔杖的魔力就大大减少,甚至没办法执行他施的魔咒,这为Ron带来了不少的烦恼。这天上魔药课,Snape要他们每人配置一种魔药(不一定是一样的),Ron因为魔杖的问题,不能完成这个任务,他请Harry在魔药课上(自然是躲过了Snape的检查)帮他配置。现在Harry面前有两个坩埚,有许多种药材要放进坩埚里,但坩埚的能力有限,无法同时配置所有的药材。一个坩埚相同时间内只能加工一种药材,但是不一定每一种药材都要加进坩埚里。加工每种药材都有必须在一个起始时间和结束时间内完成(起始时间所在的那一刻和结束时间所在的那一刻也算在完成时间内),每种药材都有一个加工后的药效。现在要求的就是Harry可以得到最大的药效。


这道题还是很考验状态的转移的。

首先我们可以很容易得出一个dp[i][j][k]表示考虑到前i个,坩埚1时间在j,坩埚2时间在k时的最大药效。

那么状态如何转移呢?反正我是看了题解才发现这个状态转移是这么巧妙与简单....

其实对于一个状态dp[i][j][k]如果这个j或者k在这个药的右端点之后的话,那么便可以从dp[i-1][j][pil[i].l-1]+pil[i].val,dp[i-1][pil[i].l-1][k]+pil[i].val与它本身的最大值转移而来。即由现在考虑到的这个药的左端点的上一秒的最大值而来。所以每次在计算dp[i][j][k],需要在之前知道比j小的j' 与比它小的k'的最大值。所以在我们需要事先对左端点进行排序,从小到大进行转移。

最后由于这道题也符合01背包倒推的性质,考虑到这一个了,只会关心比j与k比现在的j与k小的状态,那么倒序枚举,可以成功优化到两维。

下附AC代码。

#include
#include
#include
#include
#define maxn 105
using namespace std;
int t,n;
struct nod
{
    int l,r;
    int val;
}pil[maxn];
bool operator < (nod a,nod b)
{
	return a.l=b ? a : b;
}
int main()
{
	scanf("%d%d",&t,&n);
	
	for(int i=1;i<=n;i++)
	scanf("%d%d%d",&pil[i].l,&pil[i].r,&pil[i].val);
	
	sort(pil+1,pil+1+n);
	for(int i=1;i<=n;i++)
	{
		for(int j=t;j>=0;j--)
		{
			for(int k=t;k>=0;k--)
			{
				if(j>=pil[i].r)
				{
					dp[j][k]=mymax(dp[j][k],dp[pil[i].l-1][k]+pil[i].val);
				}
				if(k>=pil[i].r)
				{
					dp[j][k]=mymax(dp[j][k],dp[j][pil[i].l-1]+pil[i].val);
				}
			}
		}
	}
	
	printf("%d\n",dp[t][t]);
}

例六: RQNOJ140 分配时间

       题目描述

小王参加的考试是几门科目的试卷放在一起考,一共给t分钟来做。他现在已经知道每门科目花的时间和得到的分数的关系,还有写名字要的时间(他写自己的名字很慢)请帮他算一下他最高能得几分。总分一定时,第一门科目成绩尽量高,第一门科目成绩也一样时,第二门科目成绩尽量高…………以次类推。如果放弃某一门的考试(花的时间为0),那么名字也就不用写了。

样例说明

第一门:0分钟;第二门:3分钟,写名字1分钟,做题目2分钟,得3分;第三门:2分钟,写名字1分钟,做题目1分钟,得3分。总共得6分。

数据范围

对于50 %的数据,n<=4,对于100 %的数据,n<=10,t<=100, 所有数据都在longint范围内。


其实这道题的内容和第三题是一模一样的,刚刚写代码的时候忽然发现这道题用了2维,再次想的时候感觉可以优化就强行删掉一维,果然是可以的。

第一步我们先设计状态,dp[i][j]表示考虑到前i个,花了j分钟所能得到的最大分数。同样是一个类似于分组背包的内容,

第二步考虑转移,由于对于一个物体只能选择一个时间,所以也可以使用01背包的方法进行转移。不过需要多一维0-任意分钟的选择。对于先枚举花的时间,还是先枚举花了多少时间的问题,可以参加例三的题解。

最后答案当然是dp[t] t秒时候的最大得分啦~

下附AC代码。

#include
#include
#include
#define maxn 125
using namespace std;
int t,n,nam;
int val[maxn][maxn];
int dp[maxn];
int main()
{
    scanf("%d%d%d",&t,&n,&nam);
	for(int i=1;i<=n;i++)
	{
		for(int j=nam+1;j<=nam+t;j++)
		scanf("%d",&val[i][j]);
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=t;j>=nam;j--)
		{
			for(int k=nam+1;k<=nam+t;k++)
			{
				if(j-k>=0)
				dp[j]=max(dp[j],dp[j-k]+val[i][k]); 
			}
		}
	}
	printf("%d\n",dp[t]);
}


终于写完了这个专题了,动态规划确实难想,想出来了也难讲啊!








你可能感兴趣的:(动态规划)