• 【CF1399E1】 Weights Division (easy version)


      印象深刻的一道题。


    题意描述

      在一棵 $n$ 个节点、根为 $1$ 号点的树上,每一条边都有边权。

      每次操作可以使一条边的边权除以 $2$ 并下取整,问最少操作多少次可以让点 $1$ 到各个叶子结点的距离和不超过 $S$。


      首先,很容易想到一个贪心策略:哪条边当前的边权 × 该边出现次数最大,就优先让这条边的边权除以 $2$。

      但是这个策略有漏洞,做法需要进行修改。

      第一步:求出点 $1$ 到各个叶子结点的路径中,每条边出现的次数。

      有一个显而易见的结论:一个点的出现次数,等于这个点到达它的父亲,经过的边的出现次数。

      而题目一开始就说了 $1$ 号点是根,所以从 $1$ 号点开始 dfs,把每条边的边权信息存到较深的节点就可以了(边权也需要传)。

      第二步:每次操作时,找到当前影响最大的边,并将其边权除以 $2$ 下取整,直到距离总和小于 $S$。

      这步也不难,我们可以拿一个堆(或 set 什么的都可以),每次只需要 $log$ 级别的复杂度就可以添加、删除节点。

      为了判断距离总和是否小于 $S$,需要做:

    • 操作前算出所有的边权 × 该边出现次数之和,命名为 $S_0$。
    • 假设当前优先级最大边的边权为 $w$,出现次数为 $c$,那么做一次操作之后,$S_0leftarrow S_0 - left(w - lfloor frac{w}{2} floor ight) imes c$。

      如果使用的是堆,我们就需要设计它的优先级,为此我曾设计过两种做法:

    • 第一种(非正确做法):把当前边权 × 该边出现次数最大的边找出来,边权变为 $frac{ ext{边权 * 该边出现次数}}{2}$

      这个做法的反例如下:

      边权 出现次数
      53 2
      15 7

      虽然 $53 imes 2 > 15 imes 7$,但是 $left(53-lfloorfrac{53}{2} floor ight) imes 2 < left(15-lfloorfrac{15}{2} floor ight) imes 7$,明显后者可以减小的数更大,即操作后作用越大,所以这个做法是错误的。

    • 第二种做法(正确做法):优先级判定应该为,选出当前最大的

    $$left( ext{边权}-lfloorfrac{ ext{边权}}{2} floor ight) imes ext{出现次数}$$

      然后,将 边权(注意除法和出现次数无关,不要拿乘积作除法) 除以 $2$ 下取整。

      程序的主体内容就结束了,剩下的就没什么难度了。


    复杂度分析

      对于每组数据(因为 $sum n le 10^5$,所以没有影响),我们有 $n$ 个数塞进堆里。

      因为每次操作都是将数字除以 $2$ 下取整,所以把数 $a$ 变为 0 的操作次数只有 $log$ 级别。

      当所有数字变为 $0$,距离和就一定不超过 $S$($S$ 是正整数)。

      所以最多在堆内操作 $n log w$ 次。

      故复杂度就是 $O((n log w) log (n log w))$,在 $3$ 秒时间限制下是可以通过的。


    代码

      

    #include <cstdio>
    #include <queue>
    #define N 100010
    typedef long long ll;
    using namespace std;
     
    struct Edge{
        int to, nxt;
        ll val;
    }e[N << 1];
    int t, n, fir[N], ans, cnt;
    ll S, s[N], sum[N], W[N], l, r, SS;
     
    struct P{
        int id;                             // 这里设 id 是为了方便查找边的出现次数 
        ll val;
    };
    priority_queue <P> q;                   // 优先队列 
    
    bool operator < (P a, P b){             // 判断优先级 
        return (a.val - a.val / 2) * s[a.id] < (b.val - b.val / 2) * s[b.id];
    }
     
    inline void add(int x, int y, ll z){    // 建边 
        e[++cnt].to = y;
        e[cnt].nxt = fir[x];
        e[cnt].val = z;
        fir[x] = cnt;
    }
     
    ll dfs(int x, int fa){                  // 统计每条边出现次数、以及和边权的乘积 
        ll ss = 0;
        bool bb = 0;
        for(int i = fir[x], y; i; i = e[i].nxt){
            y = e[i].to;
            if(y == fa) continue;           // 对于每次搜索不要返回去搜索父亲,不然死循环 
            bb = 1;                         // 确定这不是叶子节点 
            ss += dfs(y, x);
            W[y] = e[i].val;                // 把边的信息化成点的信息,方便之后统计 
            sum[y] = s[y] * e[i].val;        
        }
        if(!bb) return s[x] = 1;            // 是叶子节点就要把自己信息带上去 
        else return s[x] = ss;              // 不然只需搬运下面的节点的信息 
    }
    
    void CLEAR(){
        for(int i = 1; i <= n; i++)
            fir[i] = s[i] = sum[i] = W[i] = 0;
        while(!q.empty())                   // 优先队列清空 
            q.pop();
    }
     
    int main(){
     
        scanf("%d", &t);
        while(t--){
            scanf("%d %lld", &n, &S);
            cnt = ans = 0, SS = 0;
            for(int i = 1, u, v; i < n; i++){
                ll w;
                scanf("%d %d %lld", &u, &v, &w);
                add(u, v, w), add(v, u, w);
            }
            dfs(1, 0);
            for(int i = 2; i <= n; i++)                 // 从 2 开始,因为 1 没有通往父亲的边 
                q.push((P){i, W[i]}), SS += sum[i];
            int xx;
            ll yy;
            while(SS > S){                              // 一直操作直到结束 
                xx = q.top().id, yy = q.top().val;
                q.pop(), ++ans;                         // 答案累加 
                SS -= yy * s[xx] - (yy / 2) * s[xx];    // 减小的量 
                q.push((P){xx, yy / 2});
            }
            CLEAR();                                    // 初始化 
            printf("%d
    ", ans);
        }
     
        return 0;
    }
  • 相关阅读:
    MyBatis学习教程
    【转载】Spring MVC 整合 Freemarker
    Java高效编程之四【C语言结构的替代】
    String相关的问题
    接口与抽象类的区别
    Java Garbage Collection基础详解------Java 垃圾回收机制技术详解
    数据库事物、隔离等级及数据库锁机制
    hadoop 多表join:Map side join及Reduce side join范例
    Java IO设计模式彻底分析 (转载)
    傅里叶变换的智慧[转]
  • 原文地址:https://www.cnblogs.com/zengpeichen/p/13547670.html
Copyright © 2020-2023  润新知