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;
}