[二进制拆分]Luogu1833 樱花

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

樱花

题目背景

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

题目描述

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

输入格式

n + 1 n+1 n+1行:

1 1 1 行:现在时间 T s T_s Ts(几时:几分),去上学的时间 T e T_e Te(几时:几分),爱与愁大神院子里有几棵樱花树 n n n。这里的 T s T_s Ts T e T_e Te 格式为:hh:mm,其中 0 ≤ h h ≤ 23 0 \leq hh \leq 23 0hh23 0 ≤ m m ≤ 59 0 \leq mm \leq 59 0mm59,且 h h , m m , n hh,mm,n hh,mm,n 均为正整数。

2 2 2 行到第 n + 1 n+1 n+1 行,每行三个正整数:看完第 i i i 棵树的耗费时间 T i T_i Ti ,第 i i i 棵树的美学值 C i C_i Ci ,看第 i i i 棵树的次数 P i P_i Pi P i = 0 P_i=0 Pi=0 表示无数次, P i P_i Pi 是其他数字表示最多可看的次数 P i P_i Pi)。

输出格式

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

输入输出样例
输入 #1

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

输出 #1

11

说明/提示

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

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

题解

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

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

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

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

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

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

但是直接将 p p p拆分为一堆 2 2 2的幂也存在问题,例如对于 13 13 13:若拆分为 1 , 2 , 4 1,2,4 1,2,4,则只能表示出 0 ∼ 7 0\sim 7 07;若拆分为 1 , 2 , 4 , 8 1,2,4,8 1,2,4,8,则会表示到 0 ∼ 15 0\sim 15 015,上述两种拆分都不是我们想要的结果。

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

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

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

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

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

代码
#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();} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ShadyPi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值