题解
Silence_in_Sea · · 题解
题解
废话不多说,开始讲题。
step -2 分析算法
首先我们分析一下这道题的算法。
很显然,这题要用 dp 做,但不能只用 dp,必须在前面进行一下排序,这题也就是一道典型的贪心 dp 题。
step -1:排除无用条件
通过读题,我们可以筛掉一些无用的条件。
因为时间越往后,每个比赛的等待时间会越来越长,所以你一定会把吃饭放到最后一个比赛结束后吃饭。
所以上来
同时,你最后一定要留出回家的时间。
所以再
step 0:分类讨论
我们可以看出,
我们通过以下代码进行快排来分类:
bool cmp1(node x,node y){
return x.c>y.c;
}
step 1.0:求出 c_i 不为 0 的比赛的个数
long long n1=0;
for(int i=1;a[i].c!=0;i++,n1++);
step 1.1:分析如何排序
我们通过读题,可以找到一些有用的条件。那么,我们现在来分析,对于第
我们可以列式。
在时刻
在时刻
因为要判断的是两个式子的大小,所以可以将相同的消掉。
化简得上面的式子为
下面的式子为
按照
bool cmp(node x1,node y){
return (x+x1.a+x1.b)*y.c<(x+y.a+y.b)*x1.c;
}
为什么这样排序就对了呢?
考虑一个存在
那么只要把第
step 1.2:预处理
除了上面的排除无用的条件的两步,还要对 dp 数组进行预处理。
自然应该先将所有数赋值
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
dp[i][j]=1e9;
}
}
dp[0][0]=0;
step 1.3:进行动态规划
在这里,
对于第
首先,我们应该将所有这场比赛不选的情况放入 dp 中。
枚举前
for(int j=0;j<=i;j++){
dp[i][j]=dp[i-1][j];
}
接着,我们应该将这场比赛选的情况更新。
枚举前
for(int j=1;j<=i;j++){
if (dp[i-1][j-1]<T){
long long t=x+a[i].a+(dp[i-1][j-1]+x)*a[i].c+a[i].b;
if (t+dp[i-1][j-1]<=T) dp[i][j]=min(dp[i][j],t+dp[i-1][j-1]);
}
}
这里注意,如果时间超过
step 1.4:优化
显然,
那么,我们怎么才能进一步优化程序呢?
由于我们要 dp 的这一部分满足
我们假设第一次打第
再假设第二次打第
再假设第三次打第
我们可以得出,对于第一次比赛,其结束时间不小于
第二次比赛结束时间不小于
第三次比赛结束时间不小于
总结一下,我们可以得出,第
所以我们就可以简化复杂度到
此复杂度的 dp 代码如下:
for(int i=1;i<=n1;i++){
for(int j=0;j<=min(31,i);j++){
dp[i][j]=dp[i-1][j];
}
for(int j=1;j<=min(31,i);j++){
if (dp[i-1][j-1]<T){
long long t=x+a[i].a+(dp[i-1][j-1]+x)*a[i].c+a[i].b;
if (t+dp[i-1][j-1]<=T) dp[i][j]=min(dp[i][j],t+dp[i-1][j-1]);
}
}
}
这里注意,
step 2.1:贪心
我们想想,当一个区间
当然是把
bool cmp2(node x,node y){
return x.a+x.b<y.a+y.b;
}
step 2.2:求前缀和
为了方便之后查找剩余时间够打几场比赛,我们应该先求出排完序后的后半部分的前缀和。
for(int i=n1+1;i<=n;i++){
s[i]=s[i-1]+a[i].a+a[i].b+x;
}
step 2.3:更新答案
枚举在
long long sum=0;
for(int i=min(31ll,n1);i>=0;i--){
long long o=dp[n1][i];
if (o>T) continue;
long long r1=T-o;
int l=n1,r=n;
while(l<r){
int mid=(l+r+1)/2;
if (s[mid]<=r1) l=mid;
else r=mid-1;
}
sum=max(sum,l-n1+i);
}
cout<<sum<<endl;