luogu 4698[CEOI2011]Hotel

1 篇文章 0 订阅
1 篇文章 0 订阅

题意:一个旅馆, n n n个房间,每个房间可容纳人数为 p i p_i pi,维护费用为 c i c_i ci。现在有 m m m个订单,每个订单消费金额为 v i v_i vi,人数为 d i d_i di。每个订单被接待了以后你需要安排一个房间,房间容量大于等于人数,你可以得到消费金额减维护费用的收益。问在接收 o o o个以内订单的情况下最大收益。
数据范围: 1 ≤ n , m ≤ 500000 1\leq n,m \leq 500000 1n,m500000 1 ≤ o ≤ m i n ( n , m ) 1\leq o \leq min(n,m) 1omin(n,m) 1 ≤ p i , v i , c i , d i ≤ 1 0 9 1 \leq p_i , v_i, c_i ,d_i \leq 10^9 1pi,vi,ci,di109,保证 ∀ i , j \forall i,j i,j,如果 p i < p j p_i<p_j pi<pj c i ≤ c j c_i\leq c_j cicj,时限 4000 4000 4000ms

解法:我们发现 n , m , o n,m,o n,m,o全特别大, n 3 n^3 n3dp是一定过不了了,这个时候我们用到一个常用的套路,把 o o o变成一个 l o g log log。具体做法就是每次选订单的时候把代价减一个常数,再记录订单总数。显然的是这个常数越大,订单数越小
。我们能这么做是因为对于每个 o o o,答案是上凸的。因为对于较小的 o o o,没选的订单中比较优的一定更大。
现在 o o o省掉了,就是一个 n n n m m m的匹配问题,经典的贪心,因为 p , c p,c p,c满足偏序关系,我们从小到大考虑 p p p,每次只要有最大的 v v v使得它有收益就匹配。用大根堆维护。
时间复杂度: O ( n l o g ( m a x ( v ) ) l o g ( n ) ) O(n log(max(v) ) log(n)) O(nlog(max(v))log(n))
代码

#include<iostream>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<time.h>
#include<algorithm>
using namespace std;
#define REP(i,x,y) for(ll i=x;i<=y;i++)
#define rep(i,n) REP(i,1,n)
#define rep0(i,n) REP(i,0,n-1)
#define repG(i,x) for(ll i=pos[x];~i;i=e[i].next)
#define ll long long
#define db double
const ll N=5e5+7;
const ll INF=1e9+7;
struct pir{
    ll v,d;
}p1[N],p2[N];
ll n,m,o;
priority_queue<ll>q;
bool cmp(pir x,pir y){
    if(x.d==y.d)return x.v<y.v;
    return x.d<y.d;
}
pir check(ll x,bool f){
    ll nw=0,ans=0,c=0;
    while(!q.empty())q.pop();
    rep(i,n){
        while(nw!=m&&p2[nw+1].d<=p1[i].d)nw++,q.push(p2[nw].v);
        if(!q.empty()){
            if(q.top()-x-p1[i].v>0){
                ans+=q.top()-x-p1[i].v;
                c++;
                q.pop();
            }
            else if(q.top()-x-p1[i].v==0&&f)c++,q.pop();
        }
    }
    return (pir){ans,c};
}
int main(){
    scanf("%lld%lld%lld",&n,&m,&o);
    rep(i,n)scanf("%lld%lld",&p1[i].v,&p1[i].d);
    rep(i,m)scanf("%lld%lld",&p2[i].v,&p2[i].d);
    sort(p1+1,p1+n+1,cmp);
    sort(p2+1,p2+m+1,cmp);
    ll L=0,R=INF;
    pir t=check(0,0);
    if(t.d<=o){printf("%lld\n",t.v); return 0;}
    while(L<R-1){
        ll mid=(L+R)>>1;
        if(check(mid,1).d>=o)L=mid;
        else R=mid;
    }
    if(check(L,0).d<=o&&check(L,1).d>=o)printf("%lld\n",check(L,0).v+L*o);
    else printf("%lld\n",check(R,0).v+R*o);
    return 0;
}

大根堆常数较大,T了两回,懒得手写的我无耻的开了O2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值