Codeforces1399 E2. Weights Division (hard version)(堆+前缀和+二分)

题意:

给定大小为n的树,树有边权w和花费c,c=1或者c=2
点1是树的根,定义树的权值为所有叶子到根的距离和
给定S,一次操作你可以将一条边的边权除以2,花费为该边权的c
问最少花费多少代价,使得树的权值<=S

数据范围:n<=1e5

解法:

用堆存储每一条边下面的叶子树,以及边权。根据c=1还是c=2分类,丢入两种堆中。
对于每种堆,优先取出减少权值最大的堆节点,记录减少量,然后将边权除以2再放回堆中。这是一次操作。
堆两种堆都记录一个最优操作序列,维护减少量的前缀和。

然后枚举c=1的堆操作多少次,根据c=2的前缀和,二分出c2操作多少次,对代价取min就是答案了。

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e6+5;
struct E{
    int v,w,c;
};
struct Node{
    int w,cnt;
    bool operator<(const Node& a)const{
        return w*cnt-w/2*cnt<a.w*a.cnt-a.w/2*a.cnt;
    }
};
priority_queue<Node>q,qq;
vector<E>g[maxm];
int sz[maxm];
int temp1[maxm],c1;//这个数组要开大一点,一条边1e6,log次除2变成0,大概开1e5*log=2e6?
int temp2[maxm],c2;
int n,S;
int sum;
void init(){
    sum=0;
    c1=c2=0;
    while(!q.empty())q.pop();
    while(!qq.empty())qq.pop();
    for(int i=1;i<=n;i++){
        g[i].clear();
    }
}
void dfs(int x,int fa){
    sz[x]=0;
    if(g[x].size()==1&&x!=1){
        sz[x]=1;
        return ;
    }
    for(auto i:g[x]){
        int v=i.v;
        if(v==fa)continue;
        dfs(v,x);
        sum+=i.w*sz[v];
        sz[x]+=sz[v];
        if(i.c==1){
            q.push({i.w,sz[v]});
        }else{
            qq.push({i.w,sz[v]});
        }
    }
}
signed main(){
    ios::sync_with_stdio(0);
    int T;cin>>T;
    while(T--){
        init();
        cin>>n>>S;
        for(int i=1;i<n;i++){
            int a,b,c,d;cin>>a>>b>>c>>d;
            g[a].push_back({b,c,d});
            g[b].push_back({a,c,d});
        }
        dfs(1,1);
        if(sum<=S){
            cout<<0<<endl;
            continue;
        }
        while(!q.empty()){
            Node x=q.top();q.pop();
            int t=x.w*x.cnt-x.w/2*x.cnt;
            temp1[++c1]=t;//记录减少量
            temp1[c1]+=temp1[c1-1];//维护前缀和
            if(sum-temp1[c1]<=S)break;
            x.w/=2;
            if(x.w)q.push(x);
        }
        while(!qq.empty()){
            Node x=qq.top();qq.pop();
            int t=x.w*x.cnt-x.w/2*x.cnt;
            temp2[++c2]=t;//记录减少量
            temp2[c2]+=temp2[c2-1];//维护前缀和
            if(sum-temp2[c2]<=S)break;
            x.w/=2;
            if(x.w)qq.push(x);
        }
        int ans=1e18;
        for(int i=0;i<=c1;i++){
            int ss=sum-temp1[i];
            int l=0,r=c2;
            int pos=1e18;
            while(l<=r){
                int mid=(l+r)/2;
                if(ss-temp2[mid]<=S)pos=mid,r=mid-1;
                else l=mid+1;
            }
            ans=min(ans,i+pos*2);
        }
        cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/107899616
今日推荐