题意:
给定大小为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;
}