有一颗有根树,每个边有边权。树的价值是根到所有叶子节点的边权和的总和即 ∑ w ( r o o t − > l e a f ) \sum w(root->leaf) ∑w(root−>leaf).你可以进行一次操作使得任意一条边的边权值除以2,求最少进行多少次操作使得树的价值小于等于s
一个常见的套路题吧,可惜自己没想出来,就是优先队列维护贪心即可
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define fi first
#define se second
#define debug printf(" I am here\n");
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-10;
int n,head[maxn],sz[maxn],cnt;
ll ans,sum,s;
priority_queue<struct node2> que;
struct node1{
int to,next,w;
}e[maxn<<1];
struct node2{
int w,sz;
friend bool operator<(node2 a,node2 b){
return 1ll*(a.w-(a.w/2))*a.sz<1ll*(b.w-(b.w/2))*b.sz;
}
};
void add(int u,int v,int w){
e[++cnt].to=v;
e[cnt].next=head[u];
e[cnt].w=w;
head[u]=cnt;
}
void init(){
while(!que.empty()) que.pop();
//一定注意要清空
cnt=ans=sum=0;
for(int i=1;i<=n;i++){
sz[i]=head[i]=0;
}
}
void dfs(int son,int fa){
bool flag=1;//判断是否为根节点
//sz[i]表示子代有几个叶子节点
for(int i=head[son];i;i=e[i].next){
if(e[i].to==fa) continue;
flag=0;
dfs(e[i].to,son);
que.push({e[i].w,sz[e[i].to]});
sum+=1ll*e[i].w*sz[e[i].to];
sz[son]+=sz[e[i].to];
}
if(flag){
sz[son]=1;
}
}
signed main(){
int _;scanf("%d",&_);
while(_--){
scanf("%d%lld",&n,&s);
init();
for(int i=1,u,v,w;i<=n-1;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
dfs(1,1);
while(sum>s){
ans++;
int w=que.top().w,sz=que.top().sz;
que.pop();
sum-=1ll*(w-(w/2))*sz;
que.push({w/2,sz});
}
printf("%lld\n",ans);
}
return 0;
}
这个和easy的题目基本一样,唯一不同的是,他的边还有一个c值,你每操作一次就会使用c值,c值为1或2,要求你最少使用多少值。
em这个题目和上一个题目基本一样,你可能也想直接用优先队列维护最优决策,在c值为2的时候除以2.但是显然这是错误的。
如这组数据
1
3 10099
1 2 10000 2
1 3 100 1
虽然将第一条边除以 2 收益更大,但是第二条边除以 2 已经满足要求了。
那么该怎么写呢,其实你可以注意c只有两个值,必定所取的操作在这两个c值所在的优先队列都是最优,你可以直接维护两个优先队列,然后遍历一个优先队列,然后二分另一个优先队列就可以解决这个问题了
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define fi first
#define se second
#define debug printf(" I am here\n");
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-10;
int n,head[maxn],sz[maxn],cnt;
ll sum,s;
ll pre1[maxn*50],pre2[maxn*50],cnt1,cnt2;
priority_queue<struct node2> que1;
priority_queue<struct node2> que2;
struct node1{
int to,next,w,c;
}e[maxn<<1];
struct node2{
int w,sz;
friend bool operator<(node2 a,node2 b){
return 1ll*(a.w-(a.w/2))*a.sz<1ll*(b.w-(b.w/2))*b.sz;
}
};
void add(int u,int v,int w,int c){
e[++cnt].to=v;
e[cnt].next=head[u];
e[cnt].w=w;
e[cnt].c=c;
head[u]=cnt;
}
void init(){
while(!que1.empty()) que1.pop();
while(!que2.empty()) que2.pop();
//一定注意要清空
cnt=cnt1=cnt2=sum=0;
for(int i=1;i<=n;i++){
sz[i]=head[i]=0;
}
}
void dfs(int son,int fa){
bool flag=1;//判断是否为根节点
//sz[i]表示子代有几个叶子节点
for(int i=head[son];i;i=e[i].next){
if(e[i].to==fa) continue;
flag=0;
dfs(e[i].to,son);
if(e[i].c==1){
que1.push({e[i].w,sz[e[i].to]});
}else{
que2.push({e[i].w,sz[e[i].to]});
}
sum+=1ll*e[i].w*sz[e[i].to];
sz[son]+=sz[e[i].to];
}
if(flag){
sz[son]=1;
}
}
signed main(){
int _;scanf("%d",&_);
while(_--){
scanf("%d%lld",&n,&s);
init();
for(int i=1,u,v,w,c;i<=n-1;i++){
scanf("%d%d%d%d",&u,&v,&w,&c);
add(u,v,w,c),add(v,u,w,c);
}
dfs(1,1);
ll tempsum=0;
while(!que1.empty()&&sum-tempsum>s){
int w=que1.top().w,sz=que1.top().sz;
que1.pop();
tempsum+=1ll*(w-(w/2))*sz;
pre1[++cnt1]=tempsum;
if(w!=1){//w=0时放进去没有意义了
que1.push({w/2,sz});
}
}
tempsum=0;
while(!que2.empty()&&sum-tempsum>s){
int w=que2.top().w,sz=que2.top().sz;
que2.pop();
tempsum+=1ll*(w-(w/2))*sz;
pre2[++cnt2]=tempsum;
if(w!=1){//w=0时放进去没有意义了
que2.push({w/2,sz});
}
}
ll ans=INF;
for(int i=0;i<=cnt1;i++){//要从0开始,可以不要选c=1
if(pre1[i]+pre2[cnt2]+s<sum) continue;
//要特判!!!!!!
int pos=lower_bound(pre2+0,pre2+1+cnt2,sum-s-pre1[i])-pre2;
ans=min(ans,1ll*i+pos*2);
}
printf("%lld\n",ans);
}
return 0;
}