【JZOJ 3463】军训

Description

HYSBZ 开学了!今年HYSBZ 有n 个男生来上学,学号为1…n,每个学生都必须参加军训。在这种比较堕落的学校里,每个男生都会有Gi 个女朋友,而且每个人都会有一个欠扁值Hi。学校为了保证军训时教官不会因为学生们都是人生赢家或者是太欠扁而发生打架事故,所以要把学生们分班,并做出了如下要求:

1.分班必须按照学号顺序来,即不能在一个班上出现学号不连续的情况。

2.每个学生必须要被分到某个班上。

3.每个班的欠扁值定义为该班中欠扁值最高的那名同学的欠扁值。所有班的欠扁值之和不得超过Limit。

4.每个班的女友指数定义为该班中所有同学的女友数量之和。在满足条件1、2、3 的情况下,分班应使得女友指数最高的那个班的女友指数最小。

请你帮HYSBZ 的教务处完成分班工作,并输出女友指数最高的班级的女友指数。

输入数据保证题目有解。

Solution

一看到这题,想:哈哈哈,又是一道最大的最小,随便搞搞~~
(10M之后…)冥思苦想ing…


这题用二分套线段树来搞(其实我觉得有一种更好的代替法),

先来看看 O(log2(4108)n2) 的方法,
二分一个t,表示所有组最多的女友指数,
fi 表示当前组以i为结尾的最小欠扁值,
Dp式:

fi=min(max(h(j+1)...i)+fj)(k=j+1igk<=t)

max用RMQ即可,
变一下:
Hi 为往左走第一个 hj>=hi 的j, Gi 为往左走 j=igj<=t 的最左位置,
Hi 的左边,我们已经都做过了,所有max的值都知道了,也就是所有的从这边转移出来的数据我们已经知道了,
那么在 (Hi+1) ~ i 这个区间内,全部的max值都是hi,这个区间转移出的数据就是 fi+max ,我们可以用线段树来记录,
我们就可以记录一下在线段树中记录每个区的 min(fi)min(fi+max)maxlazy
每次打上区间的max值,就要把之前打上的max值覆盖掉,
注意打max值时,有可能会有 Hi<Gi1 的情况,这时要去max,
DP式就是:
fi=min(fj+max)(Gi1)<=j<i

细节有点多,
复杂度: O(log2(4108)nlog2(n))

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=20500,M=15,maxlongint=2147483640;
int read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n,ans;
int h[N],g[N],H[N],G[N];
int f[N];
int za[N];
int b[N*4],ad[N*4],la[N*4],bs[N*4];
int min(int a,int b){return a>b?b:a;}
int max(int a,int b){return a<b?b:a;}
void doit(int l,int r,int e)
{
    if(!la[e])return;
    b[e]=bs[e];
    ad[e]=la[e];
    if(l!=r)la[e*2]=max(la[e*2],la[e]),la[e*2+1]=max(la[2*e+1],la[e]);
    la[e]=0;
}
void HB(int l,int r,int e)
{
    if(l==r)return;
    bs[e]=min(bs[e*2],bs[e*2+1]);
    if(b[e*2]+ad[e*2]>b[e*2+1]+ad[e*2+1])b[e]=b[e*2+1],ad[e]=ad[e*2+1];
    else b[e]=b[e*2],ad[e]=ad[e*2];
}
void modify(int l,int r,int e,int l1,int r1,int l2)
{
    if(r<l)return;
    if(la[e])doit(l,r,e);
    if(l==l1&&r==r1)
    {
        la[e]=l2;doit(l,r,e);
        return;
    }
    int t=(l+r)/2;
    if(r1<=t)modify(l,t,e*2,l1,r1,l2),doit(l,r,e*2+1);
        else if(t<l1)doit(l,r,e*2),modify(t+1,r,e*2+1,l1,r1,l2);
            else 
            {
                modify(l,t,e*2,l1,t,l2);
                modify(t+1,r,e*2+1,t+1,r1,l2);
            }
    HB(l,r,e);
}
int find(int l,int r,int e,int l1,int r1)
{
    if(la[e])doit(l,r,e);
    if(l==l1&&r==r1)return b[e]+ad[e];
    int t=(l+r)/2,ans=maxlongint;
    if(r1<=t)ans=find(l,t,e*2,l1,r1),doit(l,r,e*2+1);
        else if(t<l1)doit(l,r,e*2),ans=find(t+1,r,e*2+1,l1,r1);
            else
            {
                ans=min(find(l,t,e*2,l1,t),find(t+1,r,e*2+1,t+1,r1));
            }
    HB(l,r,e);
    return ans;
}
void change(int l,int r,int e,int l1,int l2)
{
    if(la[e])doit(l,r,e);
    if(l==r&&l==l1)
    {
        b[e]=bs[e]=l2;ad[e]=0;
        return;
    }
    int t=(l+r)/2;
    if(l1<=t)change(l,t,e*2,l1,l2),doit(l,r,e*2+1);
        else if(t<l1)doit(l,r,e*2),change(t+1,r,e*2+1,l1,l2);
    HB(l,r,e);
}
bool OK(int t)
{
    memset(b,0,sizeof(b));
    memset(ad,0,sizeof(ad));
    memset(la,0,sizeof(la));
    memset(bs,0,sizeof(bs));
    int w=0,q=1;
    fo(i,1,n)
    {
        w+=g[i];while(w>t)w-=g[q++];
        G[i]=q;
    }
    modify(0,n,1,0,0,h[1]);
    f[1]=h[1];change(0,n,1,1,f[1]);
    fo(i,2,n)
    {
        modify(0,n,1,max(H[i],G[i]-1),i-1,h[i]);
        f[i]=find(0,n,1,G[i]-1,i-1);
        change(0,n,1,i,f[i]);
    }
    if(f[n]>m)return 0;
    return 1;
}
int main()
{
    int q,w,l,r;
    read(n),read(m);
    l=r=0;
    fo(i,1,n)read(h[i]),r+=read(g[i]),l=max(l,g[i]);    
    za[0]=0;
    fo(i,1,n)
    {
        while(za[0]&&h[za[za[0]]]<h[i])za[0]--;
        H[i]=za[za[0]];za[++za[0]]=i;
    }
    while(l<r)
    {
        int t=(l+r)/2;
        if(OK(t))r=t;else l=t+1;
    }
    printf("%d\n",l);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值