[IOI2008] Island

算法

题意可以转化成
给定一个基环树森林, 求每颗基环树上的直径长度之和

找环

按照基环树的方法找即可

求直径

(i) 直径不经过环

对于以环上每一个点的子树, 记录直径即可

(ii) 直径经过环

断环为链, 考虑单调队列处理, 具体的
pADh5ng.webp

关于为什么需要断环为链:
方便快速处理环上两点间的距离, 显然不复制两份正确性会有问题
本质上是处理优弧劣弧的 trick

最后的答案即为 ( \(dis\) 为环上距离前缀和, \(D\) 为子树直径, \(Size\) 为环长)

\[\max{(dis_i - dis_j + D_i + D_j, i - j + 1 \leq Size)} \]

\[\Downarrow \]

\[\max{(D_i + dis_i + (D_j - dis_j), i - j + 1 \leq Size)} \]

可以使用单调队列, 类似滑动窗口 dp 的方式优化

代码实现细节

这题感觉不太好打, 在这里理一下思路

先用并查集标记一下不同的基环树(毕竟是森林)
全图跑一遍无向图上的拓扑排序
找到环之后, 对于每个环(即每棵基环树), 计算以环上的每一点为根的树的直径, 即 \(D_i\)
在计算的同时, 用 bfs 遍历这个环将其化成链
这个可以用网络流里那种反向边 \(\oplus 1\) 的方法解决(感谢伟大 洛谷题解 的思路)
预处理一下 \(dis\) 数组
对于每一棵树, 答案累加

代码

后补

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

int N;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = x * 10 + ch - '0', ch = getchar();
    return x * f;
}

class Graph_Class
{
private:

public:
    /*链式前向星*/
    struct Edge_node
    {
        int to, w;
        int next;
    } Edge[MAXN << 1];
    int Edge_cnt = -1;
    int head[MAXN];
    void head_init()
    {
        for (int i = 1; i <= N; i++)
            head[i] = -1;
    }

    /*并查集*/
    struct Graph_Dsu
    {
        int fa[MAXN];
        void init() {
            for (int i = 1; i <= N; i++)
                fa[i] = i;
        }

        int find(int x) { return fa[x] = (x == fa[x]) ? x : find(fa[x]); }
        void merge(int u, int v)
        {
            int fa_u = find(u), fa_v = find(v);
            if (fa_u != fa_v)
                fa[fa_u] = fa_v;
        }
    } Dsu;

    /*-----------------------------------------------------------------------------------------------------------------------------*/

    int Graph_Degree[MAXN];
    /*所需的初始化*/
    void init() { head_init(), Dsu.init(); }

    /*加边函数*/
    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;
        Dsu.merge(u, v);
        Graph_Degree[u]++;
    }
} Graph;

int Ans = 0;

class Doit_Class
{
private:
    /*单调队列*/
    struct MonoQueue_Struct
    {
        struct node
        {
            int Val;
            int Pos;
        };
        std::deque<node> q;

        void insert(node x, int Size)
        {
            /*去尾*/
            while(!q.empty() && x.Val > q.back().Val)
                q.pop_back();
            q.push_back(x);
        }

        void update(int Now, int Size)
        {
            /*删头*/
            while (!q.empty() && Now - q.front().Pos + 1 > Size) // 是否需要 i - j + 1 值得思考
                q.pop_front();
        }
    } MonoQueue;

public:
    /*全图上的 拓扑排序, 最后没有进过队的就是环上的点*/
    bool OnCir[MAXN];
    std::queue<int> Q;
    void topo(int NowGraph)
    {
        while(!Q.empty()) Q.pop(); //
        for(int i = 1; i <= N; i++) {
            if(Graph.Dsu.find(i) != NowGraph) {
                OnCir[i] = false;
            }
            else { //
                OnCir[i] = true; 
            }
        }
        /*注意无向图上的实现差别*/
        for (int i = 1; i <= N; i++)
            if (Graph.Graph_Degree[i] == 1 && Graph.Dsu.find(i) == NowGraph)
                Q.push(i), OnCir[i] = false;

        while(!Q.empty())
        {
            int Now = Q.front();
            Q.pop();

            OnCir[Now] = false;
            for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
            {
                int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
                if (!OnCir[NowTo] || Graph.Dsu.find(NowTo) != NowGraph)
                    continue;

                if(--Graph.Graph_Degree[NowTo] == 1)
                    Q.push(NowTo), OnCir[NowTo] = false;
            }
        }
    }


    struct Cir
    {
        int Now;
        int Nxt, w;
    } List[MAXN << 1];
    int List_cnt = 0;
    int D[MAXN], Dis[MAXN];
    bool Vis_Cir[MAXN << 1]; // 记录边的访问情况
    int List_St = -1;

    /*化环为链 + 计算子树直径*/
    void FindCir(int NowGraph, int Now)
    {
        for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
        {
            int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
            /*不要反向边我们共建新香港*/
            if(Vis_Cir[i ^ 1] || Graph.Dsu.find(NowTo) != NowGraph || !OnCir[NowTo])
                continue;

            if(NowTo == List_St) {
                List[++List_cnt].Now = Now;
                List[List_cnt].Nxt = NowTo;
                List[List_cnt].w = NowW;
                Vis_Cir[i] = true;
                return;
            }
            List[++List_cnt].Now = Now;
            List[List_cnt].Nxt = NowTo;
            List[List_cnt].w = NowW;
            Vis_Cir[i] = true;
            FindCir(NowGraph, NowTo);
        }
    }

    /*计算链上距离前缀和*/
    void CalcDis()
    {
        for (int i = 1; i <= List_cnt; i++) {
            List[List_cnt + i].Now = List[i].Now;
            List[List_cnt + i].Nxt = List[i].Nxt;
            List[List_cnt + i].w = List[i].w;
        }
        for (int i = 1; i <= List_cnt * 2; i++)
            Dis[i] = Dis[i - 1] + List[i].w;
    }

    int NowD = 0;
    /*树形 dp*/
    void CalcD(int Now, int fa, int NowGraph)
    {
        for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next)
        {
            int NowTo = Graph.Edge[i].to, NowW = Graph.Edge[i].w;
            if (NowTo == fa || Graph.Dsu.find(NowTo) != NowGraph || OnCir[NowTo])
                continue;
            
            CalcD(NowTo, Now, NowGraph);
            NowD = std::max(NowD, D[Now] + D[NowTo] + Graph.Edge[i].w);
            D[Now] = std::max(D[Now], D[NowTo] + Graph.Edge[i].w);
        }
    }

    int CalcAns()
    {
        int NowAns = 0;
        while(!MonoQueue.q.empty()) MonoQueue.q.pop_front();
        /*当前点 i*/
        MonoQueue.insert((MonoQueue_Struct::node){D[List[1].Now], 0}, List_cnt);
        for (int i = 1; i <= List_cnt * 2 - 1; i++)
        {
            if(MonoQueue.q.empty()) continue;
            MonoQueue.update(i, List_cnt);

            NowAns = std::max(NowAns, D[List[i].Nxt] + Dis[i] + MonoQueue.q.front().Val);
            MonoQueue.insert((MonoQueue_Struct::node){D[List[i].Nxt] - Dis[i], i}, List_cnt);
        }

        return NowAns;
    }

    int doit(int NowGraph)
    {
        int Ans_withoutroot = 0, Ans_withroot = 0; // 答案

        /*找环*/
        topo(NowGraph);

        /*化链*/
        List_St = -1;
        for (int i = 1; i <= N; i++)
            if(OnCir[i] && Graph.Dsu.find(i) == NowGraph) {
                List_St = i;
                break;
            }
        List_cnt = 0;
        // memset(Vis_Cir, false, sizeof(Vis_Cir)); // 可以删去
        FindCir(NowGraph, List_St);

        /*--------------------------------------------------------------------------------------------------------------------*/

        /*计算链上距离前缀和*/
        CalcDis();

        /*计算 D*/
        memset(D, 0, sizeof(D));
        for (int i = 1; i <= List_cnt; i++)
            NowD = 0, CalcD(List[i].Now, -1, NowGraph), Ans_withoutroot = std::max(Ans_withoutroot, NowD);

        Ans_withroot = CalcAns();

        return std::max(Ans_withoutroot, Ans_withroot);
    }

    bool Vis[MAXN];
    void solve()
    {
        /*对于每一棵基环树, 进行处理*/
        for (int i = 1; i <= N; i++) {
            int NowGraph = Graph.Dsu.find(i);
            if (!Vis[NowGraph])
                Ans += doit(NowGraph), Vis[NowGraph] = true; //
        }

        printf("%lld", Ans);
    }
} Doit;

signed main()
{
    N = read();
    Graph.init();
    for (int i = 1; i <= N; i++) {
        int u, w;
        u = read(), w = read();
        Graph.addedge(i, u, w), Graph.addedge(u, i, w);
    }

    Doit.solve();

    return 0;
}

/*
7
3 8
7 2
4 2
1 4
1 9
3 4
2 3

24
*/

总结

基环树板子 + 复杂的环上处理, 单调队列的应用

posted @ 2024-11-01 20:59  Yorg  阅读(4)  评论(0编辑  收藏  举报