bzoj1791,P4381-[IOI2008]Island【基环树,树形dp,单调队列dp,树的直径】

正题

评测记录:https://www.luogu.org/recordnew/lists?uid=52918&pid=P4381


题目大意

有n个岛,n条无向边(保证每个岛都有边连到)。走过的路和岛不可以重走,可以坐船。
坐船要求之前没有任何使用过的船加上道路可以到达那个点才可以坐船。

求最长可以走多远。


解题思路

首先这是一棵基环树森林,根据乘船的规定其实就是每棵基环树只可以走一次。这时候我们就可以发现答案就是每棵基环树的直径之和。然后我们考虑如何计算直径。

将环看作根。我们发现答案只有两种可能

  1. 经过根
  2. 不经过根

我们考虑不经过根的,计算出 f x f_x fx( f x f_x fx表示x子树中离x最远的点的距离),然后用求树的直径的方法求出根以下每棵子树的直径并记录。然后我们计算经过根的。
假设环的节点为 s s s集合,那么答案就是 m a x ( f s i + f s j + d i s i ∼ j ) max(f_{s_i}+f_{s_j}+dis_{i\sim j}) max(fsi+fsj+disij)
dis表示环上i到j的最远距离。
我们可以通过单调队列dp计算出答案。


code

#include
#include
#include
#include
#define N 1000010
#define lls long long
using namespace std;
struct node{
    int to,next,w;
}a[2*N];
int n,x,y,tot,t;
lls ls[N],in[N],cr[2*N],v[N],k[N],f[N],d[N],dis[2*N],ans,q[2*N];
void addl(int x,int y,int w)//加边
{
    a[++tot].to=y;
    a[tot].w=w;
    a[tot].next=ls[x];
    ls[x]=tot;
    in[y]++;
}
void dfs(int now,int k){
    v[now]=k;
    for (int i=ls[now];i;i=a[i].next){
        int y=a[i].to;
        if(!v[y]) dfs(y,k);
    }
}//标记联通块
void topsort(){
	int l=0,r=0;
    for (int i=1;i<=n;i++) 
	  if(in[i]==1) q[++r]=i;
    while(l<r) {
        int now=q[++l];
        for (int i=ls[now];i;i=a[i].next){
            int y=a[i].to;
            if(in[y]>1){
                in[y]--;
                d[v[now]]=max(d[v[now]],f[now]+f[y]+a[i].w);
                f[y]=max(f[y],f[now]+a[i].w);
                if(in[y]==1) q[++r]=y;
            }
        }
    }
}//拓扑排序求环
void dp(int t,int x){
    int m=0,y=x,i;
    do{
        cr[++m]=f[y];in[y]=1;
        for(i=ls[y];i;i=a[i].next){
            if(in[a[i].to]>1){
                dis[m+1]=dis[m]+a[i].w;
                y=a[i].to;
                break;
            }
        }
    }while(i);//预处理环的数据
    if(m==2){
        int l=0;
        for (int i=ls[y];i;i=a[i].next) 
            if(a[i].to==x) l=max(l,a[i].w);
        d[t]=max(d[t],f[x]+f[y]+l);
        return;
    }//特批
    for(int i=ls[y];i;i=a[i].next){
        if(a[i].to==x) {
            dis[m+1]=dis[m]+a[i].w;
            break;
        }
    }//连接首尾
    for (int i=1;i<=m;i++){
            cr[i+m]=cr[i];
            dis[m+i]=dis[m+1]+dis[i];
    }//复制一份放在后面
    int l=1,r=0;
    q[++r]=1;
    for (int i=2;i<2*m;i++){
        while(l<=r&&i-q[l]>=m)
          l++;
        d[t]=max(dis[i]-dis[q[l]]+cr[i]+cr[q[l]],d[t]);
        while(l<=r&&cr[q[r]]+dis[i]-dis[q[r]]<=cr[i])
          r--;
        q[++r]=i;
    }//单调队列dp
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        scanf("%d%d",&x,&y);
        addl(i,x,y);
        addl(x,i,y);
    }
    for (int i=1;i<=n;i++) 
	  if (!v[i]) dfs(i,++t);
    topsort();
    for (int i=1;i<=n;i++){
        if(in[i]>1&&!k[v[i]]) {
            k[v[i]]=1;
            dp(v[i],i);
            ans+=d[v[i]];
        }
    }
    printf("%lld",ans);
}

你可能感兴趣的:(dp,数据结构,图论)