• AcWing 352/loj 10131. 「一本通 4.4 例 2」暗的连锁


    Description

    给定一棵 (n) 个点的树,还有 (m) 条非树边,问有多少种方法使得仅砍去一条树边和一条非树边使得这个图分成不相连的两(或更多)部分。

    每次如果先砍去主要边后已经砍成两半,则仍要再砍一条附加边。

    Solution

    一看这数据范围,暴力组合肯定不可行。

    思考附加边的作用:在这棵仅有主要边的数上,增加了一条附加边就会多一个带有根节点的环。

    具体看一个例子:

    如图,在增加边 ((4,5)) 后,出现了 (1-2-4-5) 的蓝颜色环,如果在加一条边 ((3,6)),则还会出现 (1-2-3-5-6) 这一个绿颜色环。

    在一个环上,要砍成两部分,就需要把这个环给打断。先切断一条主要边,再把形成这个环的那个附加边去除。因此把环一分为二需要断2条边才能断开。

    (sum[i]) 表示以 (1) 为根,(i) 号点与它的父亲节点连接的主要边砍掉后,还要砍掉多少条附加边才能把这个图一分为二。

    即每新增一条附加边,由于环上的点需要把这条附加边砍去才能断开这个环,所以这个环的每一条主要边的 (sum) 值均需要 (+1)

    具体长这个样子:

    最后要把sum的值分三类:

    • (sum[i] = 0),表示这个点没被任意一个环覆盖过,说明删掉这条边就已经变成两部分了,这个点有 $m $ 种方案。

    • (sum[i] = 1),表示这个点被一个环覆盖过,只能删除这个边后再删那个组成环的附加边,这个点有$ 1$ 种方案。

    • (sum[i] ≥ 2),表示这个点被多个环覆盖过,则不管怎样删也不能把图一分为二,这个点没有方案贡献。

    现在就是考虑如何维护这个 (sum) 数组。

    不难想到用树上差分+前缀和来写。

    这里的前缀和采取的是第二种,即从以这个点为根的子树的和。

    根据树上差分知识,可以把该条附加边连接的两点 (x)(y)(sum)(+1),把 (LCA(x, y)) 的值 (-2)

    然后用DFS跑一边数上前缀和就行了。

    Code

    // by pjx Jul.
    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <queue>
    #include <stack>
    #define REP(i, x, y) for(register int i = x; i < y; i++)
    #define rep(i, x, y) for(register int i = x; i <= y; i++)
    #define PER(i, x, y) for(register int i = x; i > y; i--)
    #define per(i, x, y) for(register int i = x; i >= y; i--)
    #define lc (k << 1)
    #define rc (k << 1 | 1)
    using namespace std;
    #define int long long
    const int N = 1E5 + 5;
    const int M = 2 * N;
    struct node{
        int u, v, next;
    }ed[M];
    int head[M], dep[N], f[N][21], sum[N];
    int n, m, cnt;
    bool b[N];
    void add(int u, int v)
    {
        cnt++;
        ed[cnt].u = u;
        ed[cnt].v = v;
        ed[cnt].next = head[u];
        head[u] = cnt;
    }
    void dfs(int x)//第一遍DFS预处理出LCA要用的f数组
    {
        for(int i = 1; i <= 20; i++)
        {
            f[x][i] = f[f[x][i - 1]][i - 1];
        }
        for(int i = head[x]; i != 0; i = ed[i].next)
        {
            int v = ed[i].v;
            if(!b[v])
            {
                b[v] = 1;
                dep[v] = dep[x] + 1;
                f[v][0] = x;
                dfs(v);
            }
        }
    }
    void dfs2(int x)//第二遍DFS就是求前缀和
    {
        for(int i = head[x]; i != 0; i = ed[i].next)
        {
            int v = ed[i].v;
            if(!b[v])
            {
                b[v] = 1;
                dfs2(v);
                sum[x] += sum[v];
            }
        }
    }
    int lca(int x, int y)
    {
        if(dep[x] < dep[y])
        {
            swap(x, y);
        }
        int temp = dep[x] - dep[y];
        for(int i = 19; i >= 0; i--)
        {
            if(temp & (1 << i))
            {
                x = f[x][i];
            }
        }
        if(x == y)
        {
            return x;
        }
        for(int i = 19; i >= 0; i--)
        {
            if(f[x][i] != f[y][i])
            {
                x = f[x][i];
                y = f[y][i];
            }
        }
        return f[x][0];
    }
    signed main()
    {
        cin >> n >> m;
        rep(i, 1, n - 1)
        {
            int x, y;
            cin >> x >> y;
            add(x, y);
            add(y, x);
        }
        b[1] = 1;
        dep[1] = 1;
        dfs(1);
        rep(i, 1, m)
        {
            int x, y;
            cin >> x >> y;
            sum[x]++;//树上差分
            sum[y]++;
            //cout << lca(x, y) << endl;
            sum[lca(x, y)] -= 2;
        }
        memset(b, 0, sizeof b);
        b[1] = 1;
        dfs2(1);
        int ans = 0;
        for(int i = 2; i <= n; i++)
        {
            if(sum[i] == 0)//分类讨论三种sum值情况
            {
                ans += m;
            }
            else if(sum[i] == 1)
            {
                ans++;
            }
        }
        cout << ans;
        return 0;
    }
    
    
  • 相关阅读:
    乐视智能电视渗透测试总结
    如何qq轰炸别人手机 如何qq多次发送消息 教你如何用记事本创建一个微信 QQ轰炸器 通信软件通用!
    如何批量重命名图片,文档,文件夹名字 Windows CMD 批量修改文件名字 内含修改路径的操作,想改哪里改哪里!超级简单的教程!
    dev编译器:c++如何让其输出小数16.84,浮点数类型数据!
    [Code Festival 2017 qual A] B: flip
    Angel Beats,AFOer Beats?
    CodeForces 860D Wizard's Tour
    bzoj3517 翻硬币
    AtCoder Regular Contest 083 D: Restoring Road Network
    AtCoder Regular Contest 083 C: Sugar Water
  • 原文地址:https://www.cnblogs.com/pjxpjx/p/15100297.html
Copyright © 2020-2023  润新知