溪染的优惠券-(状态dp+细节)

该博客探讨了一种动态规划的解决方案,用于在给定的优惠卷策略下找到购买商品的最小花费。通过比较优惠卷的价值差,对优惠卷进行排序,并使用一维或二维动态规划状态转移来确定最佳组合。博客强调了正确枚举和状态优化的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

J

题意:
给出n个优惠卷,当你目前需要花费的钱m>va[i].fi的时候,你可以用这个优惠卷,每个优惠卷只能用一次。求最小花费多少可以买到商品。

思考:
很明显,如果只拿大的肯定不行,这种题目一看就知道需要排序。但是怎么排序呢?这里就想到国王的游戏这个题目,就是两个物品,假设先用i再用j是比先用j再用i好的,然后把式子列出来看看到底是怎么样的。m-va[i].se>=va[j].fi && m-va[j].se<va[i].fi,整理一下得va[i].fi-va[i].se>va[j].fi-va[j].se。所以就按这个排序,其实你感性的理解一下,肯定选那种差距较大的卷优先,然后再放差距小的卷,就像瓶子里放石头,肯定先放大石头再放小石头才能把瓶子装的最满。这里值得注意的是不能直接贪心遍历一遍,因为这是不对的,仔细看看感觉想背包,又一想感觉这就是状态dp,所以枚举遍历一遍,看看最小的状态可行值是多少即可。其实dp状态就定义为:用到第i个优惠卷,j价钱是否可以得到。转移的话就是要么用要么不用。发现,每次用的都是上一层的i,故可以优化到一维,但是要注意顺序,优化后要记得正序转移,也就是从x来转移,这个x必须是上一层的。

代码:

一维:
int T,n,m;
PII va[N];
int dp[N];

bool cmp(PII A,PII B)
{
	return A.fi-A.se>B.fi-B.se;
}

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>va[i].fi>>va[i].se;
	sort(va+1,va+1+n,cmp);
	dp[m] = 1;
	for(int i=1;i<=n;i++)
	{
		for(int j=va[i].fi;j<=m;j++) //每个卷只能用一次
		dp[j-va[i].se] |= dp[j]; //所以必须从前往后,如果从后往前,那么一个卷可能用两次
	} //比如90可以从100过来,但是你倒叙j,所以80又可以从刚才的90过来,所以必须正序。
	for(int i=0;i<=m;i++)
	{
		if(dp[i])
		{
			cout<<i;
			break;
		}
	}
	return 0;
}

二维:
int T,n,m,k;
PII va[1005];
int dp[1005][10005]; //用到第i个优惠卷,j价钱是否可以得到

bool cmp(PII A,PII B)
{
	return A.se-A.fi<B.se-B.fi;
}

signed main()
{
	IOS;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>va[i].fi>>va[i].se;
	sort(va+1,va+1+n,cmp);
	dp[0][m] = 1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dp[i][j] |= dp[i-1][j]; //如果不用
			if(j>=va[i].fi) dp[i][j-va[i].se] |= dp[i-1][j]; //如果用
		}
	}
	for(int i=0;i<=m;i++)
	{
		if(dp[n][i])
		{
			cout<<i;
			break;
		}
	}
	return 0;
}

总结:
一定要好好考虑,怎么样才是最佳,该怎么枚举才能是正确的方案。多思考一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值