[二进制拆分]Luogu1833 樱花

本文介绍了一道关于在限定时间内,通过选择特定樱花树观赏次数以最大化美学值的算法问题。通过对多重背包问题的二进制拆分优化,实现高效算法解决方案。

原题链接:https://www.luogu.com.cn/problem/P1833

樱花

题目背景

《爱与愁的故事第四弹·plant》第一章。

题目描述

爱与愁大神后院里种了 nnn 棵樱花树,每棵都有美学值 CiC_iCi 。爱与愁大神在每天上学前都会来赏花。爱与愁大神可是生物学霸,他懂得如何欣赏樱花:一种樱花树看一遍过,一种樱花树最多看 AiA_iAi 遍,一种樱花树可以看无数遍。但是看每棵樱花树都有一定的时间 TiT_iTi 。爱与愁大神离去上学的时间只剩下一小会儿了。求解看哪几棵樱花树能使美学值最高且爱与愁大神能准时(或提早)去上学。

输入格式

n+1n+1n+1行:

111 行:现在时间 TsT_sTs(几时:几分),去上学的时间 TeT_eTe(几时:几分),爱与愁大神院子里有几棵樱花树 nnn。这里的 TsT_sTsTeT_eTe 格式为:hh:mm,其中 0≤hh≤230 \leq hh \leq 230hh230≤mm≤590 \leq mm \leq 590mm59,且 hh,mm,nhh,mm,nhh,mm,n 均为正整数。

222 行到第 n+1n+1n+1 行,每行三个正整数:看完第 iii 棵树的耗费时间 TiT_iTi ,第 iii 棵树的美学值 CiC_iCi ,看第 iii 棵树的次数 PiP_iPiPi=0P_i=0Pi=0 表示无数次,PiP_iPi 是其他数字表示最多可看的次数 PiP_iPi)。

输出格式

只有一个整数,表示最大美学值。

输入输出样例
输入 #1

6:50 7:00 3
2 1 0
3 3 1
4 5 4

输出 #1

11

说明/提示

100%100\%100% 数据:Te−Ts≤1000T_e-T_s \leq 1000TeTs1000(即开始时间距离结束时间不超过 100010001000 分钟),n≤10000n \leq 10000n10000。保证 Te,TsT_e,T_sTe,Ts 为同一天内的时间。

样例解释:赏第一棵樱花树一次,赏第三棵樱花树 222 次。

题解

退役老咸鱼含泪复习多重背包及其二进制拆分。

多重背包即指一个背包可以反复使用有限次,朴素的做法是这个背包能用多少次就做几次010101背包,若背包个数为nnn,总容量为VVV,每个背包能被重复使用的次数为pip_ipi,那么这个算法的复杂度为O(n×V×∑pi)O(n\times V\times \sum p_i)O(n×V×pi),这个∑pi\sum p_ipi就显得十分恐怖。

对于一个可以使用ppp次的背包,我们并不需要将其拆分为ppp个背包,实际上存在更简单的拆分方法,就是二进制拆分。

我们可以将拆分一个可以使用ppp次背包的问题简化为:将数ppp拆分为若干个数之和,使0∼p0\sim p0p中的所有数都能由被拆分出的数相加得到,二进制拆分就能解决这个问题。

例如:将131313拆分为1,2,4,61,2,4,61,2,4,6就能通过这四个数之间的加和得到0∼130\sim 13013之间的所有数。

为什么是二进制?因为对于拆分出的数,加和时只有“加”与“不加”这两个选项;对于一个二进制数,它的每一位也只有“000”和“111”这两个选项,这两者之间有很好的对应关系,所以一堆222的幂:1,2,4,⋯ ,2n−11,2,4,\cdots,2^{n-1}1,2,4,,2n1就可以表示出0∼2n−10\sim 2^n-102n1中的所有数。

但是直接将ppp拆分为一堆222的幂也存在问题,例如对于131313:若拆分为1,2,41,2,41,2,4,则只能表示出0∼70\sim 707;若拆分为1,2,4,81,2,4,81,2,4,8,则会表示到0∼150\sim 15015,上述两种拆分都不是我们想要的结果。

从上面的例子可以看到,131313的正确拆分为1,2,4,61,2,4,61,2,4,6666131313在拆去1,2,41,2,41,2,4后剩余的部分,所以正确的拆分姿势为:从111开始将该数先拆分为递增的222的幂,直到无法继续拆分时将剩下的部分单独作为拆分出的一个数。

以下为该拆分正确性的简要说明:

首先,由于1,2,41,2,41,2,4的存在,0∼70\sim 707中的所有数便都可以表示出来。

如果我们要凑出8∼138\sim 13813中的数,不妨反过来想,当我们把所有拆分出的数加在一起时和为131313,要凑出8∼138\sim 13813就是从中减去一个范围在0∼50\sim 505的数,显然用1,2,41,2,41,2,4可以轻松凑出0∼50\sim 505,把他们从全集中拿掉,我们便得到了8∼138\sim 13813

由此,我们便完成了二进制拆分,因为我们是以倍增的方式拆分,所以一个可以用ppp次的背包拆出来的背包数量大约为log2plog_2^plog2p个,大大优化了复杂度。

代码
#include<bits/stdc++.h>
using namespace std;
const int M=1e5+5;
int hs,he,ms,me,n,cot,tim,ans;
int dp[1005];
struct bag{int t,c;bool inf;}flo[M];
void div(int a,int b,int c)
{
	for(int i=1;c>=i;c-=i,i<<=1)flo[++cot]=(bag){a*i,b*i,0};
	flo[++cot]=(bag){a*c,b*c,0};
}
void in()
{
	scanf("%d:%d%d:%d%d",&hs,&ms,&he,&me,&n);
	for(int i=1,a,b,c;i<=n;++i)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(c)div(a,b,c);
		else flo[++cot]=(bag){a,b,1};
	}
}
void ac()
{
	tim=he*60+me-hs*60-ms;
	dp[0]=1;
	for(int i=1;i<=cot;++i)
	{
		if(flo[i].inf){for(int j=0;j<=tim;++j)if(j+flo[i].t<=tim&&dp[j])dp[j+flo[i].t]=max(dp[j+flo[i].t],dp[j]+flo[i].c);}
		else for(int j=tim;j>=0;--j)if(j+flo[i].t<=tim&&dp[j])dp[j+flo[i].t]=max(dp[j+flo[i].t],dp[j]+flo[i].c);
	}
	for(int i=0;i<=tim;++i)ans=max(ans,dp[i]);
	printf("%d",ans-1);
}
int main(){in(),ac();} 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值