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

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

这道题真的很巧妙,如果不看题解,就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在此谢谢您的仔细阅读了

原文地址:https://www.cnblogs.com/ssw02/p/11227791.html