zoukankan      html  css  js  c++  java
  • 题解——暗的连锁(树上差分)

    题解——暗的连锁(树上差分)

    这道题真的很巧妙,如果不看题解,就ssw02的智商,是完全想不到的


    题目搬运: POJ3417

    Dark 是一张无向图,图中有 N 个节点和两类边,一类边被称为主要边,而另一类被称为附加边。Dark 有 N–1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark 还有 M条附加边。

    你的任务是把 Dark 斩为不连通的两部分。一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。但是你的能力只能再切断 Dark 的一条附加边。

    现在你想要知道,一共有多少种方案可以击败 Dark。注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark。

    输入格式
    第一行包含两个整数 N 和 M;
    之后 N–1 行,每行包括两个整数 A 和 B表示 A 和 B 之间有一条主要边;
    之后 M 行以同样的格式给出附加边。

    输出格式
    输出一个整数表示答案。

    解题思路

    性质的分析:

    1.主要边的集合构成了一棵树(显然),而附加边就是图上的非树边。

    2.先割掉主要边和先割掉附加边是等效的。

    3.附加边与主要边会共同组成环的结构。

    合法情况:

    满足下面两个条件:

    1.由于环的存在,每次切掉一条附加边(X,Y),那么主要边上节点X到节点Y中必须被切掉一条边。

    2.这条主要边不会同时在2个及以上的环中出现(即不是环的交叉部分)

    简化抽象问题:

    我们可以将原题抽象成以下覆盖问题:(本段参考一本通提高篇

    给定一个无向图和一颗生成树,求每条树边被非树边覆盖了几次(附加边(X,Y)将X,Y间的主要边路径覆盖一次)。

    问题解决:

    一个典型的树上区间的修改,我们可以考虑用——>树上差分。 (逃避树链剖分)

    本题中具体而言,建立出主要边形成的树,就是对于每一条附加边(X,Y),将 X , Y 的权值+1 , LCA 的权值 -2 。(初值都为0)。

    然后,然后,没有然后 , 跑DFS求出子树权值和f[]就行了 。f[ X ]表示 X 和 father[ X ]所连的边被覆盖了多少次。

    统计答案:如果f[x]>=2 ,无论如何切都不行(环的交集) ,f[x]=1 对答案贡献+1 , ,f[x]=0 ,任意切一条即可 ,贡献为 M

    由于ssw02懒得找图,想不明白就自己画画就出来了

    AC code:

    #include <bits/stdc++.h>//为什么从LOJ拷贝下来码风就变了??
    using namespace std;
    const int MAXN = 100005;
    int N, M, head[MAXN], to[MAXN * 2], nex[MAXN * 2], tot = 1;
    int anc[MAXN][20], f[MAXN], dep[MAXN], val[MAXN], ans = 0;
    inline int read() {
        int s = 0;
        char g = getchar();
        while (g > '9' || g < '0') g = getchar();
        while (g >= '0' && g <= '9') s = s * 10 + g - '0', g = getchar();
        return s;
    }
    void add(int x, int y) { to[++tot] = y, nex[tot] = head[x], head[x] = tot; }
    void dfs(int u, int fa) { // 跑深度,用于求 LCA
        anc[u][0] = fa;
        for (register int i = 1; i <= 17; ++i) anc[u][i] = anc[anc[u][i - 1]][i - 1];
        for (register int i = head[u]; i; i = nex[i]) {
            if (to[i] == fa)
                continue;
            dep[to[i]] = dep[u] + 1;
            dfs(to[i], u);
        }
    }
    void dfs2(int u, int fa) { // 求f[]数组+统计答案
        f[u] = val[u];
        for (register int i = head[u]; i; i = nex[i]) {
            if (to[i] == fa)
                continue;
            dfs2(to[i], u);
            f[u] += f[to[i]];
        }//如果f[x]>=2 ,无论如何切都不行(环的交集) ,f[x]=1 对答案贡献+1 , ,f[x]=0 ,任意切一条即可 ,贡献为 M
        if (f[u] == 1 && u != 1)
            ans++;
        if (f[u] == 0 && u != 1)
            ans += M;
    }
    int lca(int x, int y) {
        if (dep[y] > dep[x])
            swap(x, y);
        int t = dep[x] - dep[y];
        for (register int p = 0; t; t >>= 1, ++p)
            if (t & 1)
                x = anc[x][p];
        if (x == y)
            return x;
        for (register int p = 17; anc[x][0] != anc[y][0]; p--)
            if (anc[x][p] != anc[y][p])
                x = anc[x][p], y = anc[y][p];
        return anc[x][0];
    }
    int main() {
        N = read(), M = read();
        int m1, m2;
        for (register int i = 1; i < N; ++i) {
            m1 = read(), m2 = read();
            add(m1, m2), add(m2, m1);
        }
        dfs(1, 1);
        for (register int i = 1; i <= M; ++i) {
            m1 = read(), m2 = read();
            val[m1] += 1, val[m2] += 1, val[lca(m1, m2)] -= 2;
        }
        dfs2(1, 1);
        cout << ans;
    }
    // 4 1 1 2 2 3 1 4 3 4
    

    本蒟蒻水平有限,叙述可能不清,如有问题可以留言

    希望各位大佬指出不足,ssw02在此谢谢您的仔细阅读了

  • 相关阅读:
    GridView 内部添加控件
    TreeList获取选中内容
    TreeList简介
    TreeList
    DEV—【GridControl 按钮列无法触发点击事件解决方案】
    dev 多行文本 MemoEdit
    DevExpress控件使用小结
    DEV常用设置
    DEV常用设置
    documentManager1注意事项
  • 原文地址:https://www.cnblogs.com/ssw02/p/11227791.html
Copyright © 2011-2022 走看看