Codeforces Round #661 (Div. 3)-E1. Weights Division (easy version)(优先队列+dfs)

1、题目链接:E1. Weights Division (easy version)

2、题目大意:给你一棵树n个点(根为1),n-1条边,有一个操作是将边的权值除以2(向下取整),要求通过任意次操作使所有叶子节点到根节点的距离和小于等于S,求最小的操作次数。

3、解题思路:很明显肯定先处理原有的距离最大的边,因为它除2后减少的更多,但是注意这是一棵树,有的边会被多次经过,那么这个时候除这个多次被经过的边可能性价比更高。那么我这时候就只需要比较**(一条边被经过次数 × 这条边的权值) − (一条边被经历的次数×(这条边的权值/2))** 就行了,又因为每条边可能被多次操作而我们只需要操作差值最大就行,所以使用优先队列实现,而对于边的经历次数我使用dfs来记录。

4、一些数据:

8 4
1 2 1
2 3 1
2 4 1
3 5 1
3 6 1
4 7 1
4 8 1
答案 3

5、代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
int hd[200010];
int d[200010];//每个节点的度数
ll di[200010];//记录每个节点被经过的次数
int vis[200010];//标记边是否经过
int cnt=0;
ll n,s;
struct eg{
    ll to,nt,w,tm;//tm保留原有的权值
    bool operator <(eg y)const{
        return (w-(w/tm)*(tm/2))<(y.w-(y.w/y.tm)*(y.tm/2));//运算符重载
    }
}g[200010];
void add(ll u,ll v,ll w)
{
    ++cnt;
    g[cnt].to=v;
    g[cnt].w=w;
    g[cnt].nt=hd[u];
    g[cnt].tm=w;
    hd[u]=cnt;
}
void init()//初始化
{
    cnt=0;
    for(int i=0;i<=2*n;i++){hd[i]=-1;d[i]=0;di[i]=0;vis[i]=0;}
}
void dfs(ll cu,ll fa)
{
    for(int i=hd[cu];~i;i=g[i].nt){
        int v=g[i].to;
        if(fa==v)continue;
        dfs(v,cu);
        di[cu]+=di[v];
        vis[i]=1;
        g[i].w=g[i].w*di[v];
    }

}
int main()
{
    int t;
    cin>>t;
    while(t--){
        cin>>n>>s;
        init();
        for(int i=1;i<n;i++){
            int u,v,w;
            cin>>u>>v>>w;
            add(u,v,w);
            add(v,u,w);
            d[u]++;d[v]++;
        }
        for(int i=1;i<=n;i++){
            if(d[i]==1){
                di[i]++;
            }
        }
        dfs(1,-1);
        ll sum=0;
        ll dis=0;
        ll ans=0;
        priority_queue<eg>qr;
        for(int i=1;i<=cnt;i++){
            if(vis[i]){sum+=g[i].w;
            qr.push(g[i]);
            }
        }
        while(sum-dis>s){
            eg tp=qr.top();qr.pop();
            eg te;
            dis+=(tp.w-(tp.w/tp.tm)*(tp.tm/2));
            te.w=(tp.w/tp.tm)*(tp.tm/2);
            te.tm=tp.tm/2;
            if(te.tm!=0)qr.push(te);
            ans++;
        }
        cout<<ans<<endl;

    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值