Island

通过题面 以及样例我们可以分析出 这道题的数据是一个基环树森林 而对于本题来说,在相同的基环树中走路,在不同的基环树间划船
因此这道题可以简化为:在基环树森林中 找到所有基环树直径之和的最大值
那么如何找基环树的直径呢?
预备工作:找到基环树中的环,用(sta)来储存
首先规定maxx为储存当前的最大值 我们可以将其分为两种情况:
(1.)没有经过环
这个时候相当于找将环删除后的每一棵子树的直径 这个时候可以用(dp)来找到 用(d)来存每一个点在不经过环的情况下的直径最大值(这样说可能有误,但意思就是这个!)
(2.)经过了环
这种情况 我们首先需要用一个(sum)数组来储存在这个环上的边权的前缀和 然后对于两个点(i<j) 他们所形成的路径的值为:(d[i]+d[j]+sum[j-1]-sum[i-1]) 注意这里(sum)里面-1了 因为我在处理前缀和的时候就相当于向前挪了一位
当j一定的时候 i,j形成的路径的值为(d[j]+sum[j-1]+d[i]-sum[i-1]) 用锐利的眼睛 你会惊奇的发现: 如果要使这个值最大 你应该使得 (d[i]-sum[i-1])尽量大 这个时候就可以用单调队列维护一个单调递减的序列,进行优化了!
细节:在进行环上的dp时 将环拆开 然后double it

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long 
using naespace std;
const int maxn=1000010;
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;
}

int n,first[maxn],next[maxn<<1],to[maxn<<1],val[maxn<<1];
int sta[maxn],top,tot=1,fa[maxn];
int maxx,d[maxn];
bool in_ring[maxn];
void add(int x,int y,int z)
{
	tot++;
	next[tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	val[tot]=z;
}
int dfn[maxn],times;
void dfs1(int x,int father)//找环
{
	dfn[x]=++times;
	for(int i=first[x];i;i=next[i])
	{
		int y=to[i];
		if(y==father)	continue;
		if(!dfn[y])
		{
			fa[y]=x;
			dfs1(y,x);
		}
		else if(dfn[y]>dfn[x])
		{
			for(int j=y;j!=x;j=fa[j])
				sta[++top]=j,in_ring[j]=true;
			sta[++top]=x,in_ring[x]=true;
		}
	}
}

void dp(int x,int fa)//计算d数组
{
	for(int i=first[x];i;i=next[i])
	{
		int y=to[i];
		if(y==fa || in_ring[y])	continue;
		dp(y,x);	
		maxx=max(maxx,d[x]+d[y]+val[i]);
		d[x]=max(d[x],d[y]+val[i]);
	}	
}
int lian[maxn<<1],sum[maxn<<1];
int double_top;
void dfs2(int x,int pos,int toward)//计算sum数组
{
	if(pos==double_top)	return ;
	for(int i=first[x];i;i=next[i])
	{
		int y=to[i];
		if(lian[pos+1]==y && i!=toward)
		{
			sum[pos]=sum[pos-1]+val[i];
			dfs2(y,pos+1,i^1);//这里i^1 相当于这条边的反向边 下次不能走这条边 
			return ;
		}
	}
}
int q[maxn<<1],head,tail,ans=0;
void work(int x)
{
	top=0;maxx=0;
	dfs1(x,0);
	for(int i=1;i<=top;i++)
		dp(sta[i],0);
	for(int i=1;i<=top;i++)
		lian[i]=lian[i+top]=sta[i];
	double_top=top*2;
	dfs2(sta[1],1,0);
	head=1;tail=0;
	for(int i=1;i<=double_top;i++)//用单调队列优化
	{
		while(head<=tail && i-q[head]>=top)	
			head++;
		if(head<=tail && sum[i-1]-sum[q[head]-1]+d[lian[i]]+d[lian[q[head]]]>maxx)
			maxx=sum[i-1]-sum[q[head]-1]+d[lian[i]]+d[lian[q[head]]];
		while(head<=tail && (i-q[tail]>=top || d[lian[i]]-sum[i-1]>d[lian[q[tail]]]-sum[q[tail]-1]))
			tail--;
		q[++tail]=i;
	}
	ans+=maxx;
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int x=read(),y=read();
		add(i,x,y);add(x,i,y);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])
			work(i);
	}
	printf("%lld",ans);
}
原文地址:https://www.cnblogs.com/mendessy/p/11729298.html