• Codeforces191 C. Fools and Roads


    传送门:>Here<

    题意:给出一颗树,和K次操作。每次操作给出a,b,代表从a到b的路径上所有边的权值都+1(边权最开始全部为0)。最后依次输出每条边最终的权值

    解题思路:

      由于n非常大,不能暴力搞。于是就有Dalao提出了树链剖分……好像很有道理

      然而,这是一道树上差分的经典题。于是就在这里介绍一下树上差分吧

      再理解树上差分之前,先来看一看普通的差分:

      给出一个全部为0的序列,每次操作给一段区间加上1,求最终序列中每个元素的值。

      考虑差分——每一次操作$[L, R]$,令差分数组$cf[L]++$,$cf[R+1]--$。最后在统计的时候,我们从头开始扫描依次加上$cf$数组的值,就会依次得到每个元素的值。为什么这样是正确的呢?所谓差分,就是通过对头尾的操作,来完成整个区间的操作。如果差分数组$cf[x]$增加了$k$,就意味着从$x$开始到最后每个元素的值都要加上$k$。减法也是一个道理。因此我在结尾R+1处-1,相当于消除了差分对除此区间以外的元素的印象,因为前面的+1和后面的-1正好互相抵消了。因此最后在统计答案时,依次加上差分数组的值就代表了当前元素的值了。这个方法的应用范围是很广的,例如覆盖问题等等

      理解了差分以后,树上差分也就是把差分放在了树上。当操作一次a到b之间的路径时,相当于$cf[a]++, cf[b]++, cf[lca(a, b)]-=2$. 此时我们的cf[i]的定义是从节点i到根节点的权值的前缀和,因此当我们操作a,b的路径时,相当于先把a到根的路径上+1,再把b到根的路径上+1,由于LCA到根的路径被重复加了两遍,因此减掉2. 统计的时候也和普通的差分一样,需要从前往后加起来得到当前边的经过次数。由于我们这里cf的数组时倒过来定义的,统计的时候也要从下往上走——在回溯的时候

      还有一个问题,为什么要把cf的定义反过来呢?为什么不能再LCA的地方+1,两个端点-1呢,统计好像更方便啊?注意,这可不是一颗二叉树。当你在LCA处打一个差分标记的时候,它的意义是它之后的点的权值全部+1,这也就囊括了它的其他子树,而别的子树可能并没有被经历。这也给我们一个提示,当我们要在子树上差分时,可以这样差分。

    Code

      由于这道题是边权而不是点权,常见的做法是先把边权转化为点权,最后统计。很恶心的是这道题由于有边的编号,不得不使用链式前向星存图。而且是无向图,空间开两倍不能忘。a->b和b->a的边的编号恰好是$ (x^1)+2 $的关系。

    /*by DennyQi*/
    #include <cstdio>
    #include <queue>
    #include <algorithm>
    #include <cstring>
    #define  r  read()
    #define  Max(a,b)  (((a)>(b))?(a):(b))
    #define  Min(a,b)  (((a)<(b))?(a):(b))
    using namespace std;
    const int MAXN = 100010;
    const int INF = 0x3f3f3f3f;
    inline int read(){
        int x = 0; int w = 1; register char c = getchar();
        while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
        if(c == '-') w = -1, c = getchar();
        while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + c - '0', c = getchar();
        return x * w;
    }
    int N,x,y,num_edge,K;
    int first[MAXN*2],next[MAXN*2],to[MAXN*2],ans[MAXN*2];
    int dep[MAXN],f[MAXN][30],cf[MAXN],val[MAXN];
    inline void add(int u, int v){
        to[++num_edge] = v;
        next[num_edge] = first[u];
        first[u] = num_edge;
    }
    void Dfs(int x, int father, int d){
        dep[x] = d;
        f[x][0] = father;
        for(int i = 1; (1<<i) <= d; ++i){
            f[x][i] = f[f[x][i-1]][i-1];
        }
        int v;
        for(int i = first[x]; i; i = next[i]){
            v = to[i];
            if(v == father) continue;
            Dfs(v, x, d+1);
        }
    }
    inline int lca(int a, int b){
        if(dep[a] < dep[b]) swap(a, b);
        for(int i = 25; i >= 0; --i){
            if((dep[a]-(1<<i)) < dep[b]) continue;
            a = f[a][i];
        }
        if(a == b) return a;
        for(int i = 25; i >= 0; --i){
            if(f[a][i] == f[b][i]) continue;
            a = f[a][i];
            b = f[b][i];
        }
        return f[a][0];
    }
    void GetAns(int x, int father){
        int v;
        for(int i = first[x]; i; i = next[i]){
            v = to[i];
            if(v == father) continue;
            GetAns(v, x);
            val[x] += val[v];
            ans[i] = val[v];
            ans[(i^1)+2] = val[v];
        }
        val[x] += cf[x];
    }
    int main(){
    //    freopen(".in","r",stdin);
        N=r;
        for(int i = 1; i < N; ++i){
            x=r,y=r;
            add(x, y);
            add(y, x);
        }
        Dfs(1, 0, 1);
        K=r; int LCA;
        while(K--){
            x=r,y=r;
            cf[x]++;
            cf[y]++;
            LCA = lca(x, y);
            cf[LCA] -= 2;
        }
        GetAns(1, 0);
        for(int i = 1; i < N; ++i){
            printf("%d ", ans[i<<1]);
        }
        return 0;
    }
  • 相关阅读:
    软件工程-事后Postmortem 会议
    软件工程-项目复审
    团队作业-冲刺博客(日更)
    团队作业-冲刺博客(任务与计划)
    团队作业-需求改进&系统设计
    软件工程团队作业-需求规格说明书
    TooBug,出撃!
    FileReader
    Javascript刷新页面的几种方法:
    软件工程-个人项目
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9381980.html
Copyright © 2020-2023  润新知