bzoj2590[Usaco2012 Feb]Cow Coupons

题意

FJ准备买一些新奶牛,市场上有N头奶牛(1<=N<=50000),第i头奶牛价格为Pi(1<=Pi<=109)。FJ有K张优惠券,使用优惠券购买第i头奶牛时价格会降为Ci(1<=Ci<=Pi),每头奶牛只能使用一次优惠券。FJ想知道花不超过M(1<=M<=1014)的钱最多可以买多少奶牛?

分析

比别人多个log系列...
答案显然有可二分性.所以先二分答案m,求购买m头牛的最小花费.
考虑先确定买哪些奶牛再确定这些奶牛中哪些用优惠券.如果已经知道了买哪些奶牛,对于每一头牛使用优惠券的收益Pi-Ci是确定的,我们把Pi-Ci排个序,获得其中最大的k个收益即可.也就是说,最后一定是Pi-Ci较大的一些牛使用优惠券.
因此我们把n头奶牛按照Pi-Ci从小到大排序.对于最优方案,一定存在一个分界点使得分界点左侧的牛要么不选要么付出Pi的代价,分界点右侧的牛要么不选要么付出Ci的代价.
然后枚举分界点,维护分界点左侧最小的m-k个Pi和分界点右侧最小的k个Ci,就可以求出最小花费.我写了一发multiset.
复杂度\(O(nlog^{2}n)\)
终于学会multiset在存在多个相同元素时怎样只删除其中一个而不删除其他相同元素了,传迭代器进去即可

你可能看了假的C++选手写的题解

#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=50005;
int n,k;ll money;
ll p[maxn],c[maxn];
int seq[maxn];
bool cmpdlt(const int &a,const int &b){
  return p[a]-c[a]<p[b]-c[b];
}//left: p right: c
bool cmpc(const int &a,const int &b){
  return c[a]<c[b];
}
int seq2[maxn];
bool check(int m){
  if(m==0)return true;
  if(m<=k){
    long long sum=0;
    for(int i=1;i<=m;++i){
      sum+=c[seq2[i]];
    }
    return sum<=money;
  }else{
    ll ans=1ll<<60;
    int g=m-k;//g cows without coupon,k cows with coupon
    multiset<ll> lbuy,rbuy,lnot,rnot;
    ll lsum=0,rsum=0;
    for(int i=1;i<=g;++i){
      lsum+=p[seq[i]];
      lbuy.insert(p[seq[i]]);
    }
    int cnt=0;
    for(int i=g+1;i<=n;++i){
      if(cnt<k){
	rsum+=c[seq[i]];rbuy.insert(c[seq[i]]);
	++cnt;
      }else{
	if(c[seq[i]]<(*rbuy.rbegin())){
	  rsum-=(*rbuy.rbegin());
	  rsum+=c[seq[i]];
	  rnot.insert((*rbuy.rbegin()));
	  rbuy.erase(--rbuy.end());
	  rbuy.insert(c[seq[i]]);
	}else{
	  rnot.insert(c[seq[i]]);
	}
      }
    }
    ans=min(ans,lsum+rsum);
    for(int i=g+1;i<=n-k;++i){
      if(p[seq[i]]<(*lbuy.rbegin())){
	lsum-=*lbuy.rbegin();
	lbuy.erase(--lbuy.end());
	lsum+=p[seq[i]];lbuy.insert(p[seq[i]]);
      }
      if(rbuy.find(c[seq[i]])!=rbuy.end()){
	rsum-=c[seq[i]];
	rbuy.erase(rbuy.find(c[seq[i]]));
	rsum+=(*rnot.begin());rbuy.insert(*rnot.begin());
	rnot.erase(rnot.begin());
      }
      ans=min(ans,lsum+rsum);      
    }
    return ans<=money;
  }
}
int main(){
  scanf("%d%d%lld",&n,&k,&money);
  for(int i=1;i<=n;++i)scanf("%lld%lld",p+i,c+i);
  for(int i=1;i<=n;++i)seq[i]=i,seq2[i]=i;
  sort(seq+1,seq+n+1,cmpdlt);sort(seq2+1,seq2+n+1,cmpc);
  int l=0,r=n;
  while(l<=r){
    int mid=(l+r)>>1;
    if(check(mid))l=mid+1;
    else r=mid-1;
  }
  printf("%d\n",l-1);
  return 0;
}

posted @ 2017-07-13 07:50  liu_runda  阅读(242)  评论(0编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难