【NOIP2013模拟联考5】军训(training) 题解+代码

55 篇文章 0 订阅
30 篇文章 0 订阅

Description

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

Input

第一行仅2 个正整数n, Limit,分别为学生数量和欠扁值之和的上限。
接下来n 行每行2 个正整数Hi,Gi,分别为学号为i 的学生的欠扁值和女友数。

Output

仅1 个正整数,表示满足分班的条件下女友指数最高的班级的女友指数。

Sample Input

4 6
4 3
3 5
2 2
2 4

Sample Output

8
【样例解释】
分班按照(1,2),(3,4)进行,这时班级欠扁值之和为4+2=6<=Limit,而女友指数最高的班级为(1,2),为8。容易看出该分班方案可得到最佳答案。

Data Constraint

对于20%的数据:n,Limit<=100
对于40%的数据:n<=1000
对于100%的数据:1<=n,Gi<=20000,1<=Hi,Limit<=10^7

Solution

有人有一种神奇的方法,可惜我不懂,我用的是线段树。
求最大最小用二分,这是肯定的。
首先,用 f[i] 表示从1到i,满足到i结尾区间的女友值的和<当前二分出的答案。(这是限制)最小的欠扁值和(即最后与lim比较的东西)。只要 f[n]<lim 那就满足答案
G[i]为女友数,H[i]为欠扁值

f[i]=min(f[j]+max(H[j]>H[i]))(k=jiG[k]<)

max(H[j]>H[i]) 表示从j到i最大欠扁值,用RMQ,40分就到手了。

然后可以用线段树优化DP
可以发现,限制可以预处理,只要维护min里面的东西就行了,
min里面又分为两部分,f和H 那就分别处理,f显然要取min,H要去max,再维护一个答案(因为在区间中,答案不一定等于f+H)还有lazy标记,就行了。
这样就完了吗?no
maxH一定是单调递减的,因为是从当前的i向左依次取max,那在修改H的时候并不是整个可行的区间,而是左边第一个H>=当前H的那个位置到当前位置,这个可以用类似线段树上二分的方法解决。
一共需要区间修改,单点修改,区间查询,线段树上二分等。
在实现中,标记下传和维护的时候,你可能会发现并不是单纯的取min或max,自己调吧

Code

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define ll long long
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define fd(i,a,b) for(ll i=a;i>=b;i--)
#define N 20100

using namespace std;
ll a[N],g[N],h[N],n,lim,rmq[N][14],f[N],ans;
int kk;
struct note{
    ll f,h,b,l;
};
note t[N*10];
void down(int v)
{
    if (t[v].l==0) return;
    t[v*2].h=max(t[v*2].h,t[v].l);t[v*2+1].h=max(t[v*2+1].h,t[v].l);
    t[v*2].l=max(t[v*2].l,t[v].l);t[v*2+1].l=max(t[v*2+1].l,t[v].l);
    t[v*2].b=t[v*2].h+t[v*2].f;t[v*2+1].b=t[v*2+1].h+t[v*2+1].f;
    t[v].l=0;
}
int fing(int v,int i,int j,int x)
{
    if (kk) return kk;
    if (i==j) return i;
    int mid=(i+j)/2;down(v);
    if (t[v*2+1].h>x) return fing(v*2+1,mid+1,j,x);
    else if (t[v*2].h>x) return fing(v*2,i,mid,x);
    return 0;
}
void finh(int v,int i,int j,int l,int r,ll x)
{
    if (i==l && j==r){
        if (t[v].h>=x) 
        {
            int k=fing(v,i,j,x);
            kk=k;
        }
        return;
    }
    if (kk) return;
    int mid=(i+j)/2;down(v);
    if (l>mid) finh(v*2+1,mid+1,j,l,r,x);
    else if(r<=mid) finh(v*2,i,mid,l,r,x);
         else finh(v*2+1,mid+1,j,mid+1,r,x),finh(v*2,i,mid,l,mid,x);
}
void change(int v,int i,int j,int l,int r,ll x)
{
    if (i==l && j==r){
        t[v].h=x;t[v].l=x;t[v].b=t[v].h+t[v].f;
        return;
    }
    int mid=(i+j)/2;down(v);
    if (r<=mid) change(v*2,i,mid,l,r,x);
    else if(l>mid) change(v*2+1,mid+1,j,l,r,x);
         else change(v*2,i,mid,l,mid,x),change(v*2+1,mid+1,j,mid+1,r,x);
    t[v].h=max(t[2*v].h,t[2*v+1].h);t[v].f=min(t[2*v].f,t[2*v+1].f);
    ll xx=t[v*2].b,yy=t[v*2+1].b;if (xx==0) xx=10000000;if(yy==0) yy=10000000;
    t[v].b=min(xx,yy);
}
void find(int v,int i,int j,int l,int r)
{
    if (i==l && j==r){
        ans=min(ans,t[v].b);return;
    }
    int mid=(i+j)/2;down(v);
    if (r<=mid) find(v*2,i,mid,l,r);
    else if(l>mid) find(v*2+1,mid+1,j,l,r);
         else find(v*2,i,mid,l,mid),find(v*2+1,mid+1,j,mid+1,r);
}
void insert(int v,int i,int j,int x,ll y)
{
    if (i==j) {
        t[v].f=y;t[v].b=t[v].f+t[v].h;return;
    }
    down(v);int mid=(i+j)/2;
    if (x<=mid) insert(v*2,i,mid,x,y);
    else insert(v*2+1,mid+1,j,x,y);
    t[v].h=max(t[2*v].h,t[2*v+1].h);t[v].f=min(t[2*v].f,t[2*v+1].f);
    ll xx=t[v*2].b,yy=t[v*2+1].b;if (xx==0) xx=10000000;if(yy==0) yy=10000000;
    t[v].b=min(xx,yy);
}
int main()
{
    scanf("%lld%lld",&n,&lim);h[0]=0;
    fo(i,1,n) scanf("%lld%lld",&a[i],&g[i]),h[i]=h[i-1]+g[i];
    fo(i,1,n) rmq[i][0]=a[i];
    fo(j,1,log2(n))
        fo(i,1,n) 
        {
            rmq[i][j]=max(rmq[i][j-1],rmq[i+(1<<j)][j-1]);
        }
    ll l=0,r=200000000;
    for(;l+1<r;)
    {
        ll m=(l+r)/2;int j=1;
        memset(t,0,sizeof(t));
        fo(i,1,n)
        {
            while (h[i]-h[j-1]>m) j++;
            if (j>i) {f[n]=lim+1;break;}
            kk=0;finh(1,1,n,j,i,a[i]);
            change(1,1,n,max(kk+1,j),i,a[i]);
            ans=100000000;find(1,1,n,j,i);f[i]=ans;
            insert(1,1,n,i+1,f[i]);
        }
        if (f[n]<=lim) r=m;else l=m;
    }
    printf("%lld",r);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值