zoukankan      html  css  js  c++  java
  • 2020牛客多校第10场C Decrement on the Tree树上路径删除

    传送门

    题意:

    给定一棵树,一次操作可以把一条路径上每一条边的边权都-1,问最少进行多少次操作能把整棵树的边权都变为0。

    题解:

    把删边操作转化为访问点操作,易知当把一条边的边权-1时,这条边的两个端点都会被访问到1次。这样一来可以把问题转化成最少访问多少次点能够把所有边权变为0。

    对于一个点来说,他对答案的贡献是怎样的呢?

    一个点连着很多条边,有一个结论就是,如果一个点所连的边权中最大的那个边权mx如果小于这个点所连接的边权总和sum的一半时,那么这个点所连接的这些便可以相互之间操作变为0,只需要判一下sum的奇偶,若为奇则需要多一次操作消去剩余的那1边权 ;否则则需要额外的2*mx-sum次来消去其不能与这个点其他相连的边消去的部分。

    因为容易发现,对于叶子结点,你想要让他所连的边变成0,那就一定要操作边权次操作,叶子节点总符合mx * 2>=sum的情况。对于非叶子节点,则有可能符合mx * 2>=sum,也可能不符合,这是因为,我们在对这棵树上原有的叶子节点进行操作时,可能会操作到一定次数使得一些边权变为0,从而相当于这条边断掉,而产生新的叶子节点,而我们知道叶子节点所连的边权是要全部加上的,只不过此时的叶子节点是新产生的,他所连的边权也是更新过后的。这就是对于非叶子结点,mx * 2>=sum时要加上2 * mx-sum,mx * 2<sum时若sum为奇数需要+1,都是因为这个结点在满足以上情况时会在某个时刻变成一个叶子节点!而叶子节点所连的边权需要全部加上!

    以上就是我对于这道题目的理解,我明白我讲的非常抽象,有点懒不想画图了!还是不是很明白的小伙伴请自行画图举例理解一下子哈(我也是自己看博客画图理解的!

    感悟:

    这道题感觉处理得真的非常巧妙吧!感觉把树上边权问题很多都是转化成点权的问题来做的,感觉这是树上很多问题的一个套路吧!希望做过这个题之后能给以后做其他一些题提供一些思路啦!

    代码:

    /****************************
    * Author : W.A.R            *
    * Date : 2020-08-10-20:47   *
    ****************************/
    /*
    1.int类型数据用%lld输入会re
    2.multiset删除不存在的东西会re
    */
    #include<stdio.h>
    #include<string.h>
    #include<math.h>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<string>
    #include<set>
    using namespace std;
    typedef long long ll;
    const int maxn=1e5+50;
    const ll mod=1e9+7;
    multiset<int>s[maxn];
    int x[maxn],y[maxn],z[maxn];ll v[maxn];
    int cal(int i){
        int tt=*s[i].rbegin();if(2*tt>=v[i])return 2*tt-v[i];else return v[i]&1;
    }
     
    int main(){
        int n,q;ll ans=0;scanf("%d%d",&n,&q);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&x[i],&y[i],&z[i]);
    		v[x[i]]+=z[i];v[y[i]]+=z[i];
            s[x[i]].insert(z[i]);s[y[i]].insert(z[i]);
        }
        for(int i=1;i<=n;i++)ans+=cal(i);printf("%lld
    ",ans/2);
        while(q--){
            int p,w;scanf("%d%d",&p,&w);
            ans-=cal(x[p])+cal(y[p]);
            s[x[p]].erase(s[x[p]].find(z[p]));s[y[p]].erase(s[y[p]].find(z[p]));v[x[p]]-=z[p];v[y[p]]-=z[p];
            z[p]=w;//这一步很重要不能少,因为修改的时候要用到z数组的!!!
            s[x[p]].insert(w);s[y[p]].insert(w);v[x[p]]+=w;v[y[p]]+=w;
            ans+=cal(x[p])+cal(y[p]);printf("%lld
    ",ans/2);
        }
        return 0;
    }
    
    
  • 相关阅读:
    c# 深拷贝与浅拷贝
    SQLServer性能优化 .net开发菜鸟总结
    Ajax自定义无刷新控件实现
    APScheduler库的详细用法
    catkin在centos中的安装
    第五次任务实现与项目总结第六组
    Javascript教程:获取当前地址栏url
    窗口处理问题
    HTML中area标签
    Asp.net中Frameset的使用小结
  • 原文地址:https://www.cnblogs.com/wuanran/p/13472622.html
Copyright © 2011-2022 走看看