题:https://codeforces.com/contest/1399/problem/E2
题意:给定一棵树,每个边权有w值和id值,分别代表边权和将改边权降为w/2的代价(1<=id<=2),当前代价nowsum为根节点到叶子节点路径的总和,问要经过最少多少id代价能将nowsum降为<=S;
分析:首先我们可以先把nowsum算出来,其次考虑用id=1和id=2来降nowsum;
假设我取了x个id=1和y个id=2,那么肯定是选前x大w的id=1,和y大的id2;
基于这样考虑,我们可以先预处理id=1和id=2move操作效益大往小的前缀和序列sum1[],sum2[];
这样就可以枚举sum1来算sum2的位置来更新答案(nlogn)
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pb push_back #define MP make_pair #define pil pair<int,ll> const int inf=0x3f3f3f3f; const ll INF=1e18; const int M=4e6+5; struct edge{ int v; ll w; int id; }; vector<edge>g[M]; ll nowsum,sz[M],sum1[M],sum2[M]; struct node{ ll x,y,z; bool operator < (const node &b)const{ return x<b.x; } }; priority_queue<node>que,que2; void dfs1(int u,int fa){ if(g[u].size()==1) sz[u]=1; for(auto it:g[u]){ int v=it.v; ll w=it.w; if(v!=fa){ dfs1(v,u); sz[u]+=sz[v]; if(it.id==1) que.push(node{(w+1)/2*sz[v],w,sz[v]}); else que2.push(node{(w+1)/2*sz[v],w,sz[v]}); nowsum+=w*sz[v]; } } } int main() { int T; scanf("%d",&T); while(T--){ int n; ll S; scanf("%d%lld",&n,&S); nowsum=0; for(int i=1;i<=n;i++) g[i].clear(),sz[i]=0; while(!que.empty()) que.pop(); while(!que2.empty()) que2.pop(); for(int i=1;i<n;i++){ int u,v,id; ll w; scanf("%d%d%lld%d",&u,&v,&w,&id); g[u].pb(edge{v,w,id}); g[v].pb(edge{u,w,id}); } dfs1(1,0); int tot1=0; while(que.size()>0){ node u=que.top(); que.pop(); if(u.x==0) break; sum1[++tot1]=u.x; u.y=u.y/2; u.x=(u.y+1)/2*u.z; que.push(u); } for(int i=1;i<=tot1;i++) sum1[i]+=sum1[i-1]; int tot2=0; while(que2.size()>0){ node u=que2.top(); que2.pop(); if(u.x==0) break; sum2[++tot2]=u.x; u.y=u.y/2; u.x=(u.y+1)/2*u.z; que2.push(u); } for(int i=1;i<=tot2;i++) sum2[i]+=sum2[i-1]; sum2[tot2+1]=INF; ll need=nowsum-S; int ans=inf; for(int i=0;i<=tot1;i++){ ll tmp=need-sum1[i]; int pos=lower_bound(sum2,sum2+1+tot2,tmp)-sum2; if(pos==tot2+1) continue; ans=min(ans,i+2*pos); } printf("%d ",ans); } return 0; }