zoukankan      html  css  js  c++  java
  • 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);
    }
    
  • 相关阅读:
    开发一款即时通讯App,从这几步开始
    即时通讯App怎样才能火?背后的技术原理,可以从这5个角度切入
    快速搭建一个“微视”类短视频 App
    iOS Push详述,了解一下?
    怒刷3000条短视频后,我终于发现网红300万点赞的套路
    如何精准实现OCR文字识别?
    30分钟彻底弄懂flex布局
    渲染管道
    游戏引擎架构Note2
    浮点数的内存表示方法
  • 原文地址:https://www.cnblogs.com/mendessy/p/11729298.html
Copyright © 2011-2022 走看看