[枚举 线段树] 51Nod1494 选举拉票

直接做不太可做,我们可以枚举一个 d 表示其他人最大值不大于 d 时的情况。 这样就好做多了,比 d 大的部分就一定要全部扣掉。然后如果自己的票数不足 d ,就需要在剩下的所有票里取最便宜的前几个,需要线段树询问前k小值的加和。
从大到小枚举 d <script type="math/tex" id="MathJax-Element-20">d</script> 即可。

#include<cstdio>
#include<vector>
#include<algorithm>
#define Fir first
#define Sec second
#define mp(x,y) make_pair(x,y)
using namespace std;
const int maxn=100005; 
vector<int> a[maxn];
pair<int,int> b[maxn];
int n,m,all;
bool _cmp(const int &x,const int &y){ return x>y; }
int res,ans;
struct node{
    int cnt,sum; node* ch[2];
    node(){ cnt=sum=0; ch[0]=ch[1]=0; }
    void maintain(){
        cnt=ch[0]->cnt+ch[1]->cnt;
        sum=ch[0]->sum+ch[1]->sum;
    }
} *root;
typedef node* P_node;
P_node Build(int L,int R){
    P_node p=new node();
    if(L==R) return p;
    int mid=(L+R)>>1;
    p->ch[0]=Build(L,mid); p->ch[1]=Build(mid+1,R);
    return p;
}
void Updata(P_node p,int L,int R,int pos,int k){
    if(R<pos||pos<L) return;
    if(L==R){ p->cnt+=k; p->sum+=L*k; return; }
    int mid=(L+R)>>1;
    Updata(p->ch[0],L,mid,pos,k); Updata(p->ch[1],mid+1,R,pos,k);
    p->maintain();
}
int Query(P_node p,int L,int R,int k){
    if(L==R) return L*k;
    int mid=(L+R)>>1;
    if(p->ch[0]->cnt>=k) return Query(p->ch[0],L,mid,k);
    return p->ch[0]->sum+Query(p->ch[1],mid+1,R,k-p->ch[0]->cnt);
}
int main(){
    freopen("51nod1494.in","r",stdin);
    freopen("51nod1494.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int x,y; scanf("%d%d",&x,&y);
        a[x].push_back(y);
    }
    root=Build(0,10000);
    all=a[0].size();
    for(int i=1;i<=100000;i++){
        sort(a[i].begin(),a[i].end(),_cmp);
        for(int j : a[i]) Updata(root,0,10000,j,1);
        b[++m]=mp(-a[i].size(),i);
    }
    sort(b+1,b+1+m);
    ans=2e9;
    for(int d=n;d>=0;d--){
        for(int i=1;i<=m;i++){
            if(a[b[i].Sec].size()<=d) break;
            while(a[b[i].Sec].size()>d){
                int t=*a[b[i].Sec].rbegin(); a[b[i].Sec].pop_back();
                res+=t; all++; Updata(root,0,10000,t,-1);
            }
        }
        if(all>d){ ans=min(ans,res); break; }
        if(d+1-all<=root->cnt) ans=min(ans,res+Query(root,0,10000,d+1-all));
    }
    printf("%lld\n",ans);
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值