Loading

[POI2008] BLO-Blockade

算法

手玩样例可以快速得知, 如果第 \(i\) 个点不是割点, 只会导致其他点 (以下设为点集 \(O\) ) 不能到达 \(i\) 点, 不会影响 \(O\) 之间的连通性

那么显然的, 我们进行分类讨论

  • \(i\) 点不是割点
    显然的, 只会造成 \(2(n - 1)\) 的贡献

  • \(i\) 点就是割点
    这种情况稍微复杂, 我们考虑 \(\rm{Tarjan}\) 算法的本质是 \(\rm{dfs}\) , 观察到在 \(\rm{dfs}\) 树中, 割点的不同子树本质上就是分割之后的点集, 令点集的个数为 \(k\) , 第 \(i\) 个点集的大小为 \(size_i\)
    点对的数量为 $ 2(n-1)+\displaystyle\sum_{i=1}^k \sum_{j=1,j\neq i}^{k}size_i \times size_j$

注意点对有序

显然的, 我们只需要 \(\mathcal{O}(n ^ 2)\) 的计算割点, \(\mathcal{O}(n ^ 2)\) 的计算贡献即可

但是最坏情况下, 计算贡献的复杂度将达到 \(n ^ 2\) , 这是不符合预期的

考虑对式子进行化简

容易观察到, \(\displaystyle\sum_{j = 1}^{k} size_j = n - size_i - 1\)

现在的答案式子为

$ 2(n-1)+\displaystyle\sum_{i=1}^k \left[size_i \times (n - size_i - 1)\right]$

代码

第一次做连通分量的题, 加油吧

计算 \(Size\) 属于一种很典的处理

#include <bits/stdc++.h>
#define int long long
const int MAXM = 5e5 + 20;
const int MAXN = 1e5 + 20;

int n, m;

class Graph_Class
{
private:

public:
    struct node
    {
        int to, w;
        int next;
    } Edge[MAXM << 1];
    int Edge_Cnt = 0;
    int head[MAXN];
    void head_init() { for (int i = 0; i <= n; i++) head[i] = -1; }

    void addedge(int u, int v, int w) {
        Edge[++Edge_Cnt].to = v, Edge[Edge_Cnt].w = w;
        Edge[Edge_Cnt].next = head[u];
        head[u] = Edge_Cnt;
    }
} Graph;

class Sol_Class
{
private:
    int low[MAXN], dfn[MAXN], num = 0;
    bool isCut[MAXN]; // 是否为割点
    int Size[MAXN];

    int Ans[MAXN];

    void init() {
        memset(low, 0, sizeof(low));
        memset(dfn, 0, sizeof(dfn));
        memset(isCut, false, sizeof(isCut));
        memset(Size, 0, sizeof(Size));
        memset(Ans, 0, sizeof(Ans));
    }

    void tarjan(int Now, int fa)
    {
        low[Now] = dfn[Now] = ++num;
        int Child_Num = 0;
        Size[Now] = 1;

        int fa_Size = n - 1;
        int LSY = 0;
        bool fa_Cut = true;

        for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
        {
            int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
            /*下降边*/
            if (!dfn[NowTo]) // 没访问过
            {
                Child_Num++;
                tarjan(NowTo, Now);
                Size[Now] += Size[NowTo];
                LSY += Size[NowTo] * (n - Size[NowTo] - 1);
                low[Now] = std::min(low[Now], low[NowTo]);

                /*它被分开 (割点)*/
                if(low[NowTo] >= dfn[Now] && Now != 1) {
                    fa_Size -= Size[NowTo];
                    isCut[Now] = true;
                    Ans[Now] += Size[NowTo] * (n - Size[NowTo] - 1);
                }else fa_Cut = false;
            }
            /*上升边*/
            else if (dfn[NowTo] < dfn[Now] && NowTo != fa) { // 记得特判父亲
                low[Now] = std::min(low[Now], dfn[NowTo]);
            }
        }
        if (Now == 1 && Child_Num >= 2) {
            Ans[Now] = LSY;
        }else if (Now != 1 && isCut[Now]){
            Ans[Now] += fa_Size * (n - fa_Size - 1);
        }

        Ans[Now] += 2 * (n - 1);
    }

public:
    void solve() {
        init();
        tarjan(1, -1);
        for (int i = 1; i <= n; i++) printf("%lld\n", Ans[i]);
    }
} Sol;

signed main()
{
    // freopen("P3469_8.in", "r", stdin);

    scanf("%lld %lld", &n, &m);
    Graph.head_init();
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%lld %lld", &u, &v);
        Graph.addedge(u, v, 0);
        Graph.addedge(v, u, 0);
    }

    Sol.solve();

    return 0; 
}

/*
5 5
1 2
2 3
1 3
3 4
4 5


*/

感冒了, 代码细节比较多, 我不在这里整理

总结

注意结论的严谨性

手推样例有助于理解题意

posted @ 2024-11-18 09:29  Yorg  阅读(22)  评论(0)    收藏  举报