试题 算法训练 黑心药商 ALGO-1002

问题描述

  JiaoShou消灭了百变怪,为爱琳世界赢得了和平,但他突然发现自己没有升级,这就意味着必须去喝药补血。爱琳世界的NPC卖的药已经不能满足他的需求了,他找到了爱琳唯一的药贩子-药加钱。药加钱的药有N种,第i种药的价格为wi单位的金币,可以恢复pi的血,但是每种药只有一瓶,并且第i种药也许含有添加剂。添加剂本身对JiaoShou没有任何影响,可是会发生i和j的添加剂混合起来,使教授减少ax的血。还好药加钱并不是太黑心,如果他的药中i和j混合使用有副作用的话。i不会再和其他药的添加剂一起产生副作用,j也是。
  也就是说,如果设添加剂有M对副作用,第i对副作用由ci1、ci2产生了ki的血量减少,把ci1,ci2列成一张表的话,那么1-N这N个数字最多在表中出现1次(也可能没有副作用)。
  现在JiaoShou有P单位金币,并且知道这M对副作用,他想知道他最多能提升多少血量?

输入格式

  第一行为三个整数N、M、P,如题目描述。
  第二行为N个整数,第i个整数为第i种药的提升血量。
  第三行为N个整数,第i个整数为第i种药的价格。
  接下来M行,每行三个数字,第i+3行为ci1、ci2、ki表示如果同时喝下ci1和ci2,JiaoShou会减少ki的血量。

输出格式

  一个整数,为JiaoShou恢复的最大血量

样例输入

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

样例输出

10

样例说明

  虽然有副作用,但是JiaoShou的钱足以把三种药都买下来喝,于是恢复了10单位的血。

  数据规模:30%的数据:M=0
  100%数据:
  1<=N<=200,M<=N div 2; P<=1000
  每种药的价格不超过P
  每种药恢复的血量在1000以内。
  输出答案保证在longint范围内。
  对于副作用对应关系,100%数据满足题目描述。


01背包问题的变式,对此我们用set储存所有dp[i][j]的方案,dp[i][j]表示以到第i个物品为止背包容量j能恢复的最大生命值。

        但要注意c1, c2可能存在先弃后取的情况,例如这个样例:

输入

3 1 2
10 10 100
1 1 1
1 3 10000

输出

110

         对此我们分析,正常储存方案的dp对于任意c1 < c2都包括只拿c1时dp最大, 同时拿c1, c2时dp最大, 但不能确定不拿c1只拿c2时得到最大dp的情况。所以,我们交换具有对应关系的c1, c2的位置后再次dp两者取大即可。

f表示关系函数,v表示副作用的hp

C++代码

#include <iostream>
#include <set>
#include <cstring>
 
using namespace std;
 
struct DP
{
	int hp;
	set<int> s;
}dp[205][1005];
 
int h[205], w[205], f[205], v[205][205];
int n, m, p;
bool vis[205];
 
int fdp()
{
	for(int i = 1; i <= n; i++)
	{
		for(int j = 0; j <= p; j++)
		{
			dp[i][j] = dp[i-1][j].hp > dp[i][j-1].hp ? dp[i-1][j] : dp[i][j-1];
			if(j >= w[i])
			{
				if(f[i] && dp[i-1][j-w[i]].s.count(f[i]))
				{
					if(dp[i-1][j-w[i]].hp+h[i]-v[i][f[i]] > dp[i][j].hp)
					{
						dp[i][j].hp = dp[i-1][j-w[i]].hp+h[i]-v[i][f[i]];
						dp[i][j].s = dp[i-1][j-w[i]].s;
						dp[i][j].s.insert(i);
					}
				}
				else
				{
					if(dp[i-1][j-w[i]].hp+h[i] > dp[i][j].hp)
					{
						dp[i][j].hp = dp[i-1][j-w[i]].hp+h[i];
						dp[i][j].s = dp[i-1][j-w[i]].s;
						dp[i][j].s.insert(i);
					}
				}
			}
		}
	}
	return dp[n][p].hp;
}
 
int main()
{
	
	cin >> n >> m >> p;
	for(int i = 1; i <= n; i++)
	{
		cin >> h[i];
	}
	for(int i = 1; i <= n; i++)
	{
		cin >> w[i];
	}
	for(int i = 1; i <= m; i++)
	{
		int a, b, c;
		cin >> a >> b >> c;
		f[a] = b, f[b] = a;
		v[a][b] = v[b][a] = c;
	}
	int ans = fdp();
	for(int i = 1; i <= n; i++)
	{
		if(!vis[i] && f[i])
		{
			vis[i] = vis[f[i]] = true;
			swap(h[i], h[f[i]]);
			swap(w[i], w[f[i]]);
		}
	}
	memset(dp, 0, sizeof(dp));
	ans = max(ans, fdp());
	cout << ans;
	return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值