题解 P4381 【[IOI2008]Island】

题目链接

为什么我感觉所有树上简单问题到了基环树上都成了毒瘤……

Solution [IOI2008]Island

题目大意:给定基环树森林,求每棵树的直径之和

动态规划-树形dp,单调队列,基环树


分析:

首先如果把"基环"去掉就是sb题

我们用\(f[u]\)表示以\(u\)为根的子树的最长链,假定有边\(e\)

\(f[u]=max\{f[e.to]+e.dist\}\)

\(ans[u]\)表示以\(u\)为根的子树的直径

\(ans[u]=max\{ans[e.to],f'[u] + f[e.to]+e.dist\}\)

一棵树直径有两种可能,它子树的直径和过它根的直径,由子树\(ans\)转移不解释,\(f'\)代表更新前的\(f\),我们用之前子树的最长链加上当前子树的最长链得到第二种情况

树形\(dp\)可以\(O(n)\)解决

然后在基环树上,直径分两种,不经过环的直径和经过环的直径

不经过环的直径我们在环上点的\(ans\)中找个\(max\)即可

如果我们要求经过环的直径,假定\(u,v\)均在环上,显然\(ans=max\{f[u]+f[v]+dis(u,v)\}\),其中\(dis(u,v)\)为环上两点两条路径中较长的那条

这玩意儿如果暴力是\(O(n^3)\)的,加上前缀和可以做到\(O(n^2)\)

由于同时考虑两个方向不好做,我们断环成链

\(ans=max\{f[u]+f[v]+sum[u]-sum[v]\ \quad v<u\}\),我们把环上距离做个前缀和得到\(sum\),然后这个东西就可以用单调队列来优化到\(O(n)\)

窝学艺不精导致的玄学\(bug:\)

\(0\)小于一个负数

问题:使用了\(vector\)所致,\(vector.size()\)返回值\(size\_t\),该类型为无符号整数,然后有符号数和无符号数运算会统一成无符号数运算……无符号数存负数就会\(GG\)

\(Trick:\)题目保证每个点出度为\(1\),如果我们建反图就是一棵外向树

#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 1e6 + 100;
typedef long long ll;
inline int read(){
    int x = 0;char c = getchar();
    while(!isdigit(c))c = getchar();
    while(isdigit(c))x = x * 10 + c - '0',c = getchar();
    return x;
}
struct Edge{int to,dist;};
vector<Edge> G[maxn],rG[maxn];
vector<int> cir[maxn];
int deg[maxn],vis[maxn],iscir[maxn],q[maxn << 1],head,tail,n,cir_tot;
ll f[maxn],ans[maxn],sum[maxn << 1],val[maxn << 1],ansout;
inline void dfs(int u){
    for(auto e : G[u]){
        if(iscir[e.to])continue;
        dfs(e.to);
        ans[u] = max(ans[u],f[u] + f[e.to] + e.dist);
        ans[u] = max(ans[u],ans[e.to]);
        f[u] = max(f[u],f[e.to] + e.dist);
    }
}
inline void topo(){
    queue<int> Q;
    for(int i = 1;i <= n;i++){
        iscir[i] = 1;   
        if(!deg[i])Q.push(i);
    }
    while(!Q.empty()){
        int u = Q.front();Q.pop();
        iscir[u] = 0;
        for(auto e : rG[u])
            if(!--deg[e.to])Q.push(e.to);
    }
    for(int i = 1;i <= n;i++)
        if(iscir[i] && !vis[i]){
            int now = i;cir_tot++;
            do{
                for(auto e : G[now])
                    if(iscir[e.to]){
                        cir[cir_tot].push_back(e.to);
                        vis[now = e.to] = 1;break;
                    }
            }while(now != i);
        }
}
inline void solve(){
    for(int id = 1;id <= cir_tot;id++){
        reverse(cir[id].begin(),cir[id].end());
        ll tmp = 0;
        for(int x : cir[id])
            dfs(x),tmp = max(tmp,ans[x]);
        for(int i = 0;i < cir[id].size();i++){
            int u = cir[id][i],pre = i == 0 ? cir[id][cir[id].size() - 1] : cir[id][i - 1];
            for(auto e : G[u])
                if(e.to == pre){sum[i] = sum[i + cir[id].size()] = e.dist;break;}
        }
        for(int i = 1;i < cir[id].size() * 2;i++)
            sum[i] += sum[i - 1];
        for(int i = 0;i < cir[id].size() * 2;i++)
            val[i] = f[cir[id][i % cir[id].size()]] - sum[i];
        q[head = tail = 1] = 0;
        for(int i = 1;i < cir[id].size() * 2;i++){
            while(head <= tail && q[head] <= (i - (int)cir[id].size()))head++;
            if(head <= tail)tmp = max(tmp,f[cir[id][i % cir[id].size()]] + sum[i] + val[q[head]]);
            while(head <= tail && val[q[tail]] <= val[i])tail--;
            q[++tail] = i;
        }
        ansout += tmp;
    }
}
inline void addedge(int from,int to,int dist){
    G[from].push_back(Edge{to,dist});
    deg[from]++;
    rG[to].push_back(Edge{from,0}); 
}
int main(){
    n = read();
    for(int u,v = 1;v <= n;v++)
        u = read(),deg[u]++,G[u].push_back(Edge{v,read()}),rG[v].push_back(Edge{u,0});
    topo();
    solve();
    printf("%lld\n",ansout);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/colazcy/p/11818362.html
今日推荐