zoukankan      html  css  js  c++  java
  • 树上差分

    一二维差分和前缀和

    算法流程

    设差分数组为(d[i])
    快速修改点a与点b之间的边权

    d[a] += c;
    d[b] += c;
    d[lca(a, b)] -= 2 * c;
    

    以一点作为根节点的子树上的点权和即为连接该点边的修改值
    eg:设差分数组初始值为0,对于下图,++ d[3],++d[4], d[2] -= 2
    对于边b,3号点的值为1,即b的改变量为1;对于边c,4号点的值为1,即c的改变量为1;对于边a,以2号点为根节点的子树点权和为0,所以a的改变量为0

    例题

    解题思路
    题目描述比较绕,简单地说对于一个图,存在树边和非树边,询问存在多少方案能够去掉一条树边和非树边能将图转变为非连通图
    通过分析可以将问题转变为每一条树边存在于多少非树边所在的环路上,即要能够快速对每一条非树边的两端点间的边上的计数器加1,从而转变为差分问题

    代码实现

    /**
     * 主要边选一条,附加边选一条
     * 如果不加入两条边之前图是不连通的,加上两条边之后变连通了,则为一种方案
     * 即使判断图是否连通是O(1)的,两条边的枚举是O(N * M)的,会TLE
     * 
     * 以上方法不但TLE,实现也较为困难,如何判断加上某条边后图是否连通?
     * 对于任意一个非树边,如果其所在环路上的树边仅处在这么一条非树边所在的环上,
     * 那么去掉这条树边和非树边即可达到切断的目的
     * 如果换上某边处在两个非树边所在环上,那么如果想要通过切断该树边切断,需要将2条非树边均切除,是不符合题意得
     * 即统计每条树边在几个非树边所在的环上,对于<=1的边累计答案
     * 非树边所在环即非树边两端点a,b形成的环路,a -> a,b最近公共祖先p -> b
     * 快速修改一段区间的数采用差分,在树上实现故为书上差分,方法为a+=c, b+=c, p-=2c
     */
     
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #include <vector>
    
    using namespace std;
    
    const int N = 1e5 + 10, M = 2e5 + 10, INF = 0x3f3f3f3f;
    
    int n, m;
    int h[N], e[N * 2], ne[N * 2], idx;
    int depth[N], f[N][17];
    int d[N];
    int ans = 0;
    
    void bfs()
    {
        queue<int> q;
    
        q.push(1);
        memset(depth, 0x3f, sizeof depth);
        depth[0] = 0, depth[1] = 1;
    
        while (q.size())
        {
            int t = q.front();
            q.pop();
    
            for (int i = h[t]; ~i; i = ne[i])
            {
                int j = e[i];
                if (depth[j] == INF)
                {
                    q.push(j);
                    depth[j] = depth[t] + 1;
                    f[j][0] = t;
    
                    for (int k = 1; k <= 16; ++ k)
                        f[j][k] = f[f[j][k - 1]][k - 1];
                }
            }
        }
    }
    int lca(int a, int b)
    {
        if (depth[a] < depth[b]) swap(a, b);
    
        for (int k = 16; ~k; -- k)
            if (depth[f[a][k]] >= depth[b])
                a = f[a][k];
        
        if (a == b) return a;
        for (int k = 16; ~k; --k )
            if (f[a][k] != f[b][k])
            {
                a = f[a][k];
                b = f[b][k];
            }
        return f[a][0];
    }
    void add(int a, int b)
    {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    /**
     * 每个点的权值均作为连接以该点为根节点的子树的那条边的权值
     * 如果为0,说明该边没有任何限制,去掉后再去掉任意一条非树边即可达到目的,故答案累加m
     * 如果为1,说明该边受到1条非树边的限制,则必须去掉对应的那条非树边,故答案累加1
     * 如果>1,说明该边受到多条非树边的限制,无法通过仅去除一条非树边达到目的,故答案累加0
     * 
     * 计算以u为根节点的子树的差分和
     * 因为树是双向边,f防止走回头路
     */
    int dfs(int u, int f)
    {
        int res = d[u];
        for (int i = h[u]; ~i; i = ne[i])
        {
            int j = e[i];
            if (j != f)
            {
                int s = dfs(j, u);
                if (s == 0) ans += m;
                else if (s == 1) ans += 1;
                res += s;
            }
        }
    
        return res;
    }
    int main()
    {
        memset(h, -1, sizeof h);
        cin >> n >> m;
        for (int i = 0; i < n - 1; ++ i)
        {
            int a, b;
            cin >> a >> b;
            add(a, b), add(b, a);
        }
        
        bfs();
    
        for (int i = 0; i < m; ++ i)
        {
            int a, b;
            cin >> a >> b;
            ++ d[a];
            ++ d[b];
            d[lca(a, b)] -= 2; 
        }
    
        dfs(1, -1);
        
        cout << ans << endl;
    
        return 0;
    }
    
  • 相关阅读:
    简单的BMCP位图图片压缩算法
    163相册验证码图片的识别手记之二 识别
    认父亲的DbParameter!!
    文件同步精灵(初版)
    163相册验证码图片的识别手记之一 去除干扰
    C#中WebService里的回车符"\r"丢失问题
    PHP 杂谈《重构改善既有代码的设计》之二 对象之间搬移特性
    PHP5计划任务离线功能的原理
    (转)程序员疫苗:代码注入
    window7环境,不安装Oracle,使用PL/SQL Developer结合oracle精简客户端,管理Oracle数据库
  • 原文地址:https://www.cnblogs.com/G-H-Y/p/15230017.html
Copyright © 2011-2022 走看看