解题思路
题目中要求最小值的最大值,这种求最大最小双最值得问题可以很容易想到用二分答案来做.那对于判断
a
n
s
ans
ans的
c
h
e
c
k
check
check是否能用贪心求解?显然对于某一连续段而言,加入一个学生,可以增加其女友指数,但也有可能使得欠扁值增大,并没有什么能够保证正确性和最优性的贪心,那么可以考虑DP,则可得到转移方程如下:(dp[i]表示到第i个学生时,最大的欠扁值)
显然转移一次复杂度为O(n),总的时间复杂度为O(n^2),显然是会超时的.
但是实际上并非所有的转移都是有效的,有的实际上是无用的,可以用单调队列来维护一个H单调递减的序列,便可以达到减少转移个数实现优化的目的.
将pos(位置)和H(欠扁值)放在二维坐标轴上,当前i点,可以从直线p以后的点转移过来(p表示满足二分的答案下距i最远的分班位置)。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<set>
#define ll long long
#define ldb long double
using namespace std;
int n,limit,l,r,ans,H[20010],g[20010],q[20010],sum[20010],dp[20010];
bool check(int mid){
int p=1,h=0,t=1;//p表示能够满足ans的分段临界位置
for(int i=1;i<=n;i++)
{
while(sum[i]-sum[p-1]>mid)p++;//临界位置向右移动
while(h<=t&&q[h]<p)h++; //临界位置的点不在新班中,不再考虑他们的欠扁值,出队
while(h<=t&&H[q[t]]<=H[i])t--; //要将i入队,为维护队列单调递减,H比i小的出队
q[++t]=i;//i入队
dp[i]=dp[p-1]+H[q[h]];//初值(不在h~t中分班)
for(int j=h;j<t;j++)
dp[i]=min(dp[i],dp[q[j]]+H[q[j+1]]);//使该点的dp值尽可能小
if(dp[i]>limit)return 0;//若最小dp值超过限制,ans不存在
}
return 1;
}
int main(){
scanf("%d%d",&n,&limit);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&H[i],&g[i]);
l=max(l,g[i]);
r=r+g[i];
sum[i]=sum[i-1]+g[i];
}
while(l<r)
{
int mid=(l+r)/2;
if(check(mid))
{
r=mid;
ans=mid;
}
else l=mid+1;
}
printf("%d",ans);
}