Codeforces Round #661 (Div. 3) E1E2. Weights Division 题解(优先队列维护贪心 二分)

easy题目链接

题目大意

有一颗有根树,每个边有边权。树的价值是根到所有叶子节点的边权和的总和即 ∑ 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;
}

hard题目链接

题目大意

这个和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;
}


你可能感兴趣的:(贪心)