CF1399E2
Nightingale_OI · · 题解
这题是一道
给你一棵树,树上每条边有对应的边权
让你对一些边进行操作,使它的边权变成
问你至少付出多少代价可以使所有叶子结点到根节点的路径权值和不超过
一条边显然可以被多个叶子结点计算答案。
观察到只有
预处理一种操作代价的边,就可以直接贪心了,毕竟操作代价相等。
可以使用 priority_queue
进行处理,贪心地选择操作产生贡献(答案变化)最大的边。
记第
对于一条边计算完答案要记得使
因为选代价为
程序每组数据复杂度
计算答案的复杂度是
代码如下 (非常的短):
#include<bits/stdc++.h>
using namespace std;
int n,m,s,l;
long long now,mst;
//xy在这里有两种意思
//在表示边是x是指向的点,y是边的编号
//在表示贡献时x是边的权值,y是边的覆盖叶节点数量
struct xy{int x,y;};
#define dv(a) (1ll*(a.x-(a.x>>1))*a.y)//计算贡献,如果更改一次则贡献是多少
bool operator < (xy a,xy b){return dv(a)<dv(b);}
xy ls;
long long sum;
int aa[101010],bb[101010],vs[101010];
long long a[2020202],as;
long long b[2020202],bs;
vector<xy>q[101010];
priority_queue<xy>p;
int dfs(int x,int fa){
int siz=1,sf=0;
for(register int i=0;i<q[x].size();++i){
if(q[x][i].x==fa)continue;
sf=1;
vs[q[x][i].y]=dfs(q[x][i].x,x);
siz+=vs[q[x][i].y];
}
return siz-sf;//返回叶节点个数
}
void doing(){
cin>>n>>mst;
for(register int i=1;i<=n;++i)q[i].clear();
for(register int i=1;i<n;++i){
scanf("%d %d %d %d",&s,&l,&aa[i],&bb[i]);
q[s].push_back((xy){l,i});
q[l].push_back((xy){s,i});
vs[i]=0;
}
dfs(1,0);
now=as=sum=0;
//计算花费为1的所有边操作时能产生的贡献降序
for(register int i=1;i<n;++i){
if(bb[i]==2)continue;
now+=1ll*aa[i]*vs[i];
p.push((xy){aa[i],vs[i]});
}
mst-=now;
while(now){
ls=p.top();p.pop();
sum+=dv(ls);
a[++as]=dv(ls);
now-=dv(ls);
ls.x>>=1;
p.push(ls);
}
while(p.size())p.pop();
now=bs=0;
//计算花费为2的所有边操作时能产生的贡献降序
for(register int i=1;i<n;++i){
if(bb[i]==1)continue;
now+=1ll*aa[i]*vs[i];
p.push((xy){aa[i],vs[i]});
}
mst-=now;
while(now){
ls=p.top();p.pop();
b[++bs]=dv(ls);
now-=dv(ls);
ls.x>>=1;
p.push(ls);
}
while(p.size())p.pop();
mst=-mst;
if(mst<=0ll){printf("0\n");return;}//一开始就符合要求,不用操作,但要判掉
s=as+bs*2+1;
//枚举要删多少花费为2的边,并得到答案
for(register int i=0;i<=bs;++i){
sum+=b[i];
while(sum-a[as]>=mst && as)sum-=a[as--];
if(sum>=mst && as+i*2<s)s=as+i*2;
}
if(s==as+bs*2+1)s=-1;
printf("%d\n",s);
}
int main(){
int t;
cin>>t;
while(t--)doing();
return 0;
}