LibreOJ #10131. 「一本通 4.4 例 2」暗的连锁 题解 树上差分

暗的连锁

题目描述

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

样例输入 #1

4 1
1 2
2 3
1 4
3 4

样例输出 #1

3

提示

对于 100% 的数据, 1 ≤ N ≤ 1 0 5 , 1 ≤ M ≤ 2 × 1 0 5 1≤N≤10^5,1≤M≤2×10^5 1N105,1M2×105。数据保证答案不超过 2 31 − 1 2^{31}−1 2311

原题

LOJ——传送门

思路

题目求的是切断一条主要边和一条附加边后使得整张图变为不连通的两部分的方案数。由于题目保证所有主要边构成一棵树,我们可以枚举所有主要边,然后判断有多少个附加边与该主要边组合后能使得整张图变为不连通的两部分。事实上,当我们枚举每个点到其父亲的主要边时,一共有三种情况:

  • 当除去直接相连的主要边之外的连通两点的路径个数 = 0 =0 =0 时,删去该主要边即可破坏整张图,任意选择一条次要边都满足条件,贡献为 m m m
  • 当除去直接相连的主要边之外的连通两点的路径个数 = 1 =1 =1 时,删去该主要边后必须删去连通两点的唯一次要边,贡献为 1 1 1
  • 当除去直接相连的主要边之外的连通两点的路径个数 > 1 >1 >1 时,删去该主要边和任意一条次要边都无法破坏整张图,贡献为 0 0 0

问题就只剩下如何求某个点到其父亲的路径个数。我们可以采用树上差分的方法,对于任意一条连接节点 u 和节点 v 的附加边,我们令 d i f [ u ] + 1 , d i f [ v ] + 1 , d i f [ L C A ( u , v ) ] − 2 dif[u]+1,dif[v]+1,dif[LCA(u,v)]-2 dif[u]+1dif[v]+1dif[LCA(u,v)]2。然后再对树上差分求前缀和,这样所得到的 d i f [ i ] dif[i] dif[i] 就记录了节点 i i i 与它父亲之间的路径个数(除去直接相连的主要边之外)。

代码

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
typedef long long ll;

const int MAX = 2e5 + 6;
int n, m;
struct TREE
{
    vector<int> e[MAX];
    int depth[MAX];
    int fa[MAX][26];
    int dif[MAX];
    void add(int u, int v) // 添加无向边
    {
        e[u].push_back(v);
        e[v].push_back(u);
    }
    void getDepth(int u, int fath) // 获得每个节点的深度及父亲
    {
        fa[u][0] = fath;
        depth[u] = depth[fath] + 1;
        for (auto v : e[u])
        {
            if (v != fath)
                getDepth(v, u);
        }
    }
    void init()
    {
        getDepth(1, 1);
        for (int i = 1; i <= 20; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                fa[j][i] = fa[fa[j][i - 1]][i - 1]; // 记录每个节点的倍增祖先
            }
        }
    }
    int lca(int x, int y)
    {
        if (depth[x] < depth[y])
            swap(x, y);
        // 先将x和y的深度调节成一致的
        for (int i = 20; i >= 0; i--)
        {
            if (depth[fa[x][i]] >= depth[y])
            {
                x = fa[x][i];
            }
        }
        // 再一起向低深度跳跃,从而找到最近公共祖先
        if (x == y)
            return x;
        for (int i = 20; i >= 0; i--)
        {
            if (fa[x][i] != fa[y][i])
            {
                x = fa[x][i];
                y = fa[y][i];
            }
        }
        return fa[x][0];
    }
    void insert(int u, int v) // 树上差分
    {
        dif[u]++;
        dif[v]++;
        dif[lca(u, v)] -= 2;
    }
    void dfs(int u) // 用dfs求出树上差分的前缀和
    {
        for (auto v : e[u])
        {
            if (v != fa[u][0])
            {
                dfs(v);
                dif[u] += dif[v];
            }
        }
    }
} tree;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> m;
    int u, v;
    for (int i = 1; i < n; i++)
    {
        cin >> u >> v;
        tree.add(u, v); // 添加主要边
    }
    tree.init();
    for (int i = 1; i <= m; i++)
    {
        cin >> u >> v;
        tree.insert(u, v); // 添加次要边
    }
    tree.dfs(1); // 树上差分求前缀和,从而求出每个节点到其父亲节点(除去直接相连的主要边之外的)的路径个数
    i64 ans = 0;
    for (int i = 2; i <= n; i++)
    {
        if (tree.dif[i] == 0) // 此时删去主要边即可破坏整张图,所以任意选择一条次要边都满足条件,贡献为m
            ans += m;
        if (tree.dif[i] == 1) // 此时删去主要边后必须删去连通两点的唯一次要边,贡献为1
            ans++;
        // 当除去直接相连的主要边之外的路径个数大于1时,删去该主要边和任意一条次要边都无法破坏整张图,贡献为0
    }
    cout << ans << '\n';

    return 0;
}
  • 44
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
洛谷p5736 【深基7.2】质数筛是一个关于质数筛法的题目,要求我们根据输入的一个正整数n,找出小于等于n的所有质数。 质数是指只能被1和自身整除的大于1的整数,比如2、3、5、7等。质数筛法是一种常见且高效的找出质数的方法。 在这道题中,我们需要使用质数筛法来找出小于等于n的所有质数。首先,我们定义一个boolean类型的数组isPrime,用来标记每个数字是否是质数。初始时,我们将isPrime数组的所有元素都设置为true。 然后,我们从2开始遍历到n,对于每个数字i,如果isPrime[i]为true,说明这个数字是质数。那么我们就需要将i的倍数都标记为false,因为这些倍数一定不是质数。具体做法是,从2*i开始,每次增加i,将对应的isPrime数组的元素都置为false。 遍历结束后,isPrime数组中为true的元素即为小于等于n的所有质数。我们可以遍历isPrime数组,将为true的下标即为质数输出即可。 这个算法的时间复杂度是O(nloglogn),相较于直接遍历每个数字并判断是否是质数的方法,时间复杂度更低,效率更高。 对于这个题目的java实现,我们可以使用一个boolean数组isPrime来标记每个数字是否是质数,使用一个ArrayList来存储所有的质数,最后将ArrayList转化为数组输出。 代码示如下: ``` import java.util.ArrayList; public class Main{ public static void main(String[] args){ int n = 100; // 输入的正整数n boolean[] isPrime = new boolean[n+1]; // 标记每个数字是否是质数的数组 ArrayList<Integer> primes = new ArrayList<>(); // 存储质数的ArrayList // 初始化isPrime数组 for(int i=2; i<=n; i++){ isPrime[i] = true; } // 质数筛法 for(int i=2; i<=n; i++){ if(isPrime[i]){ primes.add(i); for(int j=2*i; j<=n; j+=i){ isPrime[j] = false; } } } // 将ArrayList转化为数组输出 int[] result = new int[primes.size()]; for(int i=0; i<primes.size(); i++){ result[i] = primes.get(i); } // 输出结果 for(int i=0; i<result.length; i++){ System.out.print(result[i] + " "); } } } ``` 这样,我们就可以过这段代码来实现洛谷p5736题目的要求,找出小于等于输入的正整数n的所有质数,并将它们按从小到大的顺序输出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值