NOIP模拟赛 军训(二分答案+单调队列优化DP)

NOIP模拟赛 军训

问题描述:

HYSBZ开学了!今年HYSBZ有n个男生来上学,学号为1…n,每个学生都必须参加军训。在这种比较堕落的学校里,每个男生都会有Gi个女朋友,而且每个人都会有一个欠扁值Hi。学校为了保证军训时教官不会因为学生们都是人生赢家或者是太欠扁而发生打架事故,所以要把学生们分班,并做出了如下要求:
1.分班必须按照学号顺序来,即不能在一个班上出现学号不连续的情况。
2.每个学生必须要被分到某个班上。
3.每个班的欠扁值定义为该班中欠扁值最高的那名同学的欠扁值。所有班的欠扁值之和不得超过Limit。
4.每个班的女友指数定义为该班中所有同学的女友数量之和。在满足条件1、2、3的情况下,分班应使得女友指数最高的那个班的女友指数最小。
请你帮HYSBZ的教务处完成分班工作,并输出女友指数最高的班级的女友指数。
输入数据保证题目有解。

题目分析:

题目中要求最小值的最大值,这种求最大最小双最值得问题可以很容易想到用二分答案来做.那对于判断ans的check是否能用贪心求解?显然对于某一连续段而言,加入一个学生,可以增加其女友指数,但也有可能使得欠扁值增大,并没有什么能够保证正确性和最优性的贪心,那么可以考虑DP,则可得到转移方程如下:

显然转移一次复杂度为O(n),总的时间复杂度为O(n^2),显然是会超时的.
但是实际上并非所有的转移都是有效的,有的实际上是无用的.
NOIP模拟赛 军训(二分答案+单调队列优化DP)_第1张图片
将pos(位置)和H(欠扁值)放在二维坐标轴上,当前i点,可以从直线p以后的点转移过来(p表示满足二分的答案距i最远的位置),在k和i之间的点转移过来的话,实际上是无用的,在这一段的最优转移是从k处切开,k以后的点一直到i为止分成一个班,因为k以后的点,i是最大的,那么在这之间一定是不会改变其最大值的,那么在k以后选取点则是最优的,同理j也如此.那么可以用单调队列来维护一个H单调递减的序列,便可以达到减少转移个数实现优化的目的.
(其实可以进一步优化,用小根堆维护dp[j]+max{H[k]| j< k<=i },i<=j< i)

代码:

#include
#include
#include
#include

using namespace std;

const int INF=(1<<30);
const int maxn=20000+10;

int read()
{
    char ch=getchar();int ret=0,flag=1;
    while(ch<'0'||ch>'9') {if(ch=='-') flag=-1; ch=getchar();}
    while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*flag;
}

int H[maxn],G[maxn],n,lim;
int dp[maxn],sum[maxn],que[maxn];

bool check(int ans)
{
    int head=1,tail=0,p=1;//p表示能够满足ans的分段临界位置
    for(int i=1;i<=n;i++) {
        while(sum[i]-sum[p-1]>ans) p++;//临界位置向右移动
        while(head<=tail&&que[head]//不能被转移到的点出队
        while(head<=tail&&H[i]>=H[que[tail]]) tail--;//要将i入队,为维护队列单调性,H比i小的出队
        que[++tail]=i;//i入队
        dp[i]=dp[p-1]+H[que[head]];//考虑特殊位置临界点处
        for(int j=head;j1]]);//使该点的dp值尽可能小
        if(dp[i]>lim) return false;//若最小dp值超过限制,ans不存在
    }
    return true;
}

int find_ans(int l,int r)//二分答案
{
    int ans;
    while(l<=r) {
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    return ans;
}

int main()
{
    n=read(),lim=read();
    int l=0,r=0;
    for(int i=1;i<=n;i++) {
        H[i]=read(),G[i]=read();
        sum[i]=sum[i-1]+G[i];
        l=max(l,G[i]);r+=G[i];
    }
    int ans=find_ans(l,r);
    printf("%d",ans);
    return 0;
}

你可能感兴趣的:(基础算法-二分,优化-单调队列优化,模拟赛)