IOI2008 Island

IOI2008 Island

这道题没仔细去想直接点开的题解,看了一会儿后发现是基环森林求直径的模板题,于是自己码了一波。

具体做法

对于一颗树我们可以直接 d f s dfs dfs求出直径,看题解求直径竟然只保存了最大值,觉得以前求直径保存最大和次大太low了,对于一看基环树,首先求出环的子树中的最大路径设为 s i s_i si,接着把环的序列倍长(套路),求出环上从序列第一个点到其他点的路径长度前缀和,设为 s u m i sum_i sumi,那么现在要做的即求出 i , j    ( i < j ) i,j ~~ (i < j) i,j  (i<j)满足 a b s ( i , j ) ≤ n abs(i,j) \leq n abs(i,j)n使得 s i + s j + s u m j − s u m i s_i+s_j+sum_j-sum_i si+sj+sumjsumi最大,那么考虑枚举 i i i,现在就是要求 s j + s u m j s_j+sum_j sj+sumj最大,这个显然可以用单调队列来维护。时间复杂度为 O ( n ) O(n) O(n)

C o d e \mathcal{Code} Code

/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019年10月20日 星期日 14时57分32秒
*******************************/
#include
#include
#include

using namespace std;

struct IO{
    template<typename T>
    IO & operator>>(T&res)
    {
        T q=1;char ch;
        while((ch=getchar())<'0' or ch>'9')if(ch=='-')q=-q;
        res=(ch^48);
        while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48);
        res*=q;
        return *this;
    }
}cin;

struct edge{
    int to,next,w;
    edge(int a=0,int b=0,int c=0):to(a),next(b),w(c){}
};

const int maxn=1e6+10;
long long n,head[maxn],cnt=1,vis[maxn],stk[maxn],top,ans,d[maxn],res,sum[maxn*2],s[maxn*2];
bool is_circle[maxn];
edge e[maxn<<1];
deque<int>que;

inline void add(int u,int v,int w)
{
    e[++cnt]=edge(v,head[u],w);
    head[u]=cnt;
}

bool dfs(register int now,register int la)
{
    vis[now]++;
    if(vis[now]==2) //出现两次为环,作为环的起点
    {
        stk[++top]=now;
        is_circle[now]=1;
        return 1;
    }
    for(register int i=head[now];i;i=e[i].next)
        if((i^1)!=la && dfs(e[i].to,i))
        {
            if(vis[now]!=2) //不是起点
            {
                stk[++top]=now,is_circle[now]=1,s[top]=e[i].w;
            }
            else //起点
            {
                s[1]=e[i].w;
                return 0;
            }
            return 1;
        }
    return 0;
}

void dp(register int now,register int fa)
{
    vis[now]=1;
    for(register int i=head[now];i;i=e[i].next)
        if(e[i].to!=fa)
        {
            if(is_circle[e[i].to]) continue;
            dp(e[i].to,now);
            ans=max(ans,d[now]+d[e[i].to]+e[i].w);
            d[now]=max(d[now],d[e[i].to]+e[i].w);
        }
}

bool check(int i,int j)//比较函数
{
    return s[i]+sum[i]<=s[j]+sum[j];
}

void push(int i)//单调队列压入i
{
    while(!que.empty() && check(que.back(),i))
        que.pop_back();
    que.push_back(i);
}

void pop(int i)//单调队列弹出不满足条件的
{
    while(!que.empty() && abs(i-que.front())>n || i==que.front())
        que.pop_front();
}


long long solve(int rt)
{
    ans=top=0;
    dfs(rt,0);
    while(!que.empty())que.pop_back();
    if(top)//有环
    {
        for(int i=1;i<=top;i++)
            dp(stk[i],0);
        sum[0]=sum[1]=0;
        for(int i=2;i<=2*top;i++)
            sum[i]=sum[i-1]+s[(i-1)%top+1];
        for(int i=1;i<=2*top;i++)
            s[i]=d[stk[(i-1)%top+1]];
        for(int i=2;i<=top;i++)
            push(i);
        for(int i=1;i<=top;i++)
        {
            pop(i);
            ans=max(ans,s[i]+s[que.front()]+sum[que.front()]-sum[i]);
            push(i+top);
        }
    }
    else//一棵树,好像没有这种情况
    {
        dp(rt,0);
        ans+=d[rt];
    }
    return ans;
}

int main()
{
    //freopen("p4381.in","r",stdin);
    //freopen("p4381.out","w",stdout);
    cin>>n;
    register int v,w;
    for(register int i=1;i<=n;i++)
    {
        cin>>v>>w;
        add(i,v,w);
        add(v,i,w);
    }
    for(register int i=1;i<=n;i++)
        if(!vis[i])
            res+=solve(i);
    printf("%lld\n",res);
    return 0;
}

你可能感兴趣的:(题解)