[IOI2008]Island

题目链接:Click here

Solution:

一句话题意:给定基环树森林,求每颗基环树的直径之和

考虑基环树求直径的方法,考虑基环树套路,把环和树分开考虑

我们先把环找出来,对环上的点的子树求直径,再求出这个点开始的最长链,这个很简单,可以用treedp做

考虑一颗基环树的直径可能有哪些情况:基环树上某棵子树的直径或是一棵子树走到另一颗子树

第一种情在我们dp的时候就处理完了,考虑第二种情况怎么做

从一颗子树走到另一颗子树肯定是要经过环上的点的,所以第二种情况必然是一种这样的形式\(f[x]+f[y]+dis(x,y)\)

其中\(f[x]\)表示以\(x\)为起点,终点在其子树内的最长链的长度,在dp中已经处理好了,\(dis(x,y)\)表示环上两点\(x,y\)的距离

我们把环拆成链来考虑,再把这条链倍长,这样它就能描述任意\(dis(x,y)\)

我们考虑记\(dis[x]\)表示\(x\)到链上第一个点的距离,那么\(dis(x,y)=dis[x]-dis[y]\)(这里我们钦定\(x\)\(y\)之后)则直径可被表述为\(f[x]+f[y]+dis[x]-dis[y]\)

倘若我们直接对于环上每个点枚举另外一个点来算直径,时间复杂度是\(O(n^2)\)的,考虑如何来优化

考虑把上面的式子转化形式:\(f[x]+dis[x]+f[y]-dis[y]\),我们确定了\(x\),则\(f[y]-dis[y]\)越大越好,那么就很显然的单调队列优化,我们用单调队列来维护\(f[x]-dis[x]\)的值即可,时间复杂度\(O(n)\)

找环可以用拓扑排序,然后本题需要开long long

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+11;
int n,cnt,head[N],d[N];
int tot,ring[N],vis[N],id[N];
int hd,tl,v[N],son[N];
ll edis,ans,dis[N],f[N],g[N];
queue<int> q;
struct Edge{int nxt,to,val;}edge[N<<1];
void ins(int x,int y,int z){
    edge[++cnt].nxt=head[x];
    edge[cnt].to=y;head[x]=cnt;
    edge[cnt].val=z;
}
void dfs(int x){
    ring[++tot]=x;vis[x]=1;id[x]=tot;
    for(int i=head[x];i;i=edge[i].nxt){
        int y=edge[i].to;
        if(id[y]==1&&id[x]==tot) edis=edge[i].val;
        if(vis[y]) continue;
        dis[tot+1]=edge[i].val;dfs(y);
    }
}
void treedp1(int x,int fa){
    for(int i=head[x];i;i=edge[i].nxt){
        int y=edge[i].to;
        if(id[y]||y==fa) continue;
        int w=edge[i].val;
        treedp1(y,x);
        if(f[x]<f[y]+w){
            g[x]=f[x];f[x]=f[y]+w;
            son[x]=y;
        }else if(g[x]<f[y]+w) g[x]=f[y]+w;
    }
}
void treedp2(int x,int fa,ll &Mx){
    if(f[x]+g[x]>Mx) Mx=f[x]+g[x];
    for(int i=head[x];i;i=edge[i].nxt){
        int y=edge[i].to;
        if(id[y]||y==fa) continue;
        int w=f[x]+edge[i].val;
        if(y==son[x]) w=g[x]+edge[i].val;
        if(f[y]<w){g[y]=f[y];f[y]=w;son[y]=x;}
        else if(g[y]<w) g[y]=w;
        treedp2(y,x,Mx);
    }
}
void work(){
    ll re=0,mxdis=0;hd=1,tl=0;
    for(int i=1;i<=tot;i++){
        treedp1(ring[i],0);
        treedp2(ring[i],0,mxdis);
    }int limit=tot;
    ring[++tot]=ring[1];
    dis[tot]=edis;
    for(int i=2;i<=limit;i++){
        ring[++tot]=ring[i];
        dis[tot]=dis[tot-limit];
    }
    for(int i=2;i<=tot;i++) dis[i]+=dis[i-1];
    for(int i=1;i<=tot;i++){
        int x=ring[i];
        while(hd<=tl&&i-v[hd]>=limit) ++hd;
        if(hd<=tl) re=max(re,f[ring[v[hd]]]-dis[v[hd]]+f[x]+dis[i]);
        while(tl>=hd&&f[ring[v[tl]]]-dis[v[tl]]<f[x]-dis[i]) --tl;v[++tl]=i;
    }re=max(re,mxdis);ans+=re;
}
void topsort(){
    for(int i=1;i<=n;i++)
        if(d[i]==1) q.push(i),vis[i]=1;
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=edge[i].nxt){
            int y=edge[i].to;
            if(vis[y]) continue;
            --d[y];if(d[y]==1) vis[y]=1,q.push(y);
        }
    }
    for(int i=1;i<=n;i++){
        if(vis[i]) continue;
        tot=0;dfs(i);work();
    }
}
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
signed main(){
    n=read();
    for(int i=1;i<=n;i++){
        int x=read(),z=read();
        ins(i,x,z),ins(x,i,z);++d[x],++d[i];
    }topsort();printf("%lld\n",ans);
    return 0;
}
posted @ 2019-10-24 19:58  DQY_dqy  阅读(110)  评论(0编辑  收藏  举报