JZOJ 5905. 【NOIP2018模拟10.15】黑暗之魂(darksoul)

Description

oi_juruo热爱一款名叫黑暗之魂的游戏。在这个游戏中玩家要操纵一名有 点生命值的无火的余灰在一张地图中探险。地图中有n个篝火(也就是存档点)。在篝火处休息可以将生命值恢复满。每个篝火都会向其他篝火的其中之一连有一条通道(显然,通道是双向的),这些篝火之间都相互可达。也就是说,这是一张n个点,n条边的无向连通图。每条通道里都有一些怪物,经过oi_juruo的分析,他得到了每条边的怪物会对他造成的伤害值 .为了向oier们表演他高超的游戏技巧,他要从任意一个篝火跑到任意另一个篝火而不在之间的篝火休息,在此期间,他会和他经过的通道中的怪物战斗并损失 的生命值。现在oi_juruo想知道,他的生命值 至少为多少,才能完成任意一条旅途。oi_juruo并不傻,他会走最安全的路。本题时限为3000ms

Input

第一行一个整数n。之后n行,每行三个整数ui,vi,ai ,表示有一条从ui 连向vi ,怪物伤害值为ai 的通道。

Output

一行一个数hp,表示无火的余灰的最小生命值。

Sample Input

5
1 2 2
2 3 2
3 4 2
1 4 1
4 5 4

Sample Output

8

样例说明
从2到5的路最危险,2 1 4 5受到了7点伤害,所以需要有8点生命值。

Data Constraint

Data Constraint

Solution

  • 由题知这是一颗环套树(n点n边,环大小 ≥ 1 \geq1 1)。

  • 题目要求的是这棵环套树的直径,再+1就是答案了。

  • 我们把该环提出来,在上面操作。

  • 那么直径要么是环上某点的子树内直径,要么是环上某两点子树内最长链再加一段环上路径。

  • 子树内直径和最长链我们先算出来,剩下的就是如何求出环上两点使之匹配出来最大值。

  • 枚举环上一点 i i i,同时维护一个指针 j j j 往后指,表示从 i + 1 i+1 i+1 j j j 是近一些,而 j j j 之后的点呢会绕环一圈来走回 i i i 更近一些。

  • 那么 i + 1 i+1 i+1 j j j 的我们用一个单调队列来求最大值,绕一周的用一个后缀最大值算就好了。

  • 时间复杂度 O ( n ) O(n) O(n) ,但常数有些大。

Code

#include<cstdio>
#include<cctype>
using namespace std;
typedef long long LL;
const int N=1e6+5;
int tot=1,top,qx,qy,node;
LL ans,mx;
bool pd;
int first[N],nex[N<<1],en[N<<1],w[N<<1];
int st[N],dfn[N],d[N],len[N],col[N],q[N];
LL suf[N],pre[N],f[N];
bool bz[N];
inline int read()
{
	int X=0,w=0; char ch=0;
	while(!isdigit(ch)) w|=ch=='-',ch=getchar();
	while(isdigit(ch)) X=(X<<1)+(X<<3)+(ch^48),ch=getchar();
	return w?-X:X;
}
inline LL max(LL x,LL y)
{
	return x>y?x:y;
}
inline void insert(int x,int y,int z)
{
	nex[++tot]=first[x];
	first[x]=tot;
	en[tot]=y;
	w[tot]=z;
}
void dfs(int x,int y)
{
	st[++top]=x;
	dfn[x]=top;
	for(int i=first[x];i && !pd;i=nex[i])
		if(i^y)
		{
			if(dfn[en[i]])
			{
				for(int j=dfn[en[i]];j<=top;j++)
				{
					d[++d[0]]=st[j];
					bz[st[j]]=true;
				}
				pd=true;
				return;
			}
			dfs(en[i],i^1);
		}
	top--;
}
void find(int x,int y,LL z,LL &num)
{
	if(z>num) num=z;
	for(int i=first[x];i;i=nex[i])
		if(en[i]^y && en[i]^x) find(en[i],x,z+w[i],num);
}
void get(int x,int y)
{
	if(y==d[0]) return;
	for(int i=first[x];i;i=nex[i])
		if(en[i]==d[y+1])
		{
			len[y]=w[i];
			get(en[i],y+1);
		}
}
void dg(int x,int y,LL z)
{
	if(z>mx) mx=z,node=x;
	col[x]=tot;
	for(int i=first[x];i;i=nex[i])
		if(en[i]^y && en[i]^x && !bz[en[i]]) dg(en[i],x,z+w[i]);
}
void dg1(int x,int y,LL z)
{
	if(z>mx) mx=z,node=x;
	for(int i=first[x];i;i=nex[i])
		if(en[i]^y && en[i]^x && col[en[i]]==tot) dg1(en[i],x,z+w[i]);
}
int main()
{
	freopen("darksoul.in","r",stdin);
	freopen("darksoul.out","w",stdout);
	int n=read();
	for(int i=1;i<=n;i++)
	{
		int x=read(),y=read(),z=read();
		insert(x,y,z);
		insert(y,x,z);
	}
	dfs(1,0);
	tot=0;
	for(int i=1;i<=d[0];i++)
	{
		for(int j=first[d[i]];j;j=nex[j])
			if(!bz[en[j]]) find(en[j],d[i],w[j],f[i]);
		node=mx=0;
		tot++;
		dg(d[i],0,0);
		dg1(node,0,0);
		ans=max(ans,mx);
		if(f[i]>ans) ans=f[i];
	}
	get(d[1],1);
	for(int i=first[d[d[0]]];i;i=nex[i])
		if(en[i]==d[1])
		{
			len[d[0]]=w[i];
			break;
		}
	for(int i=2;i<=d[0];i++) pre[i]=pre[i-1]+len[i-1];
	for(int i=2;i<=d[0];i++) suf[i]=pre[d[0]]+len[d[0]]-pre[i]+f[i];
	for(int i=d[0]-1;i>1;i--) suf[i]=max(suf[i+1],suf[i]);
	int l=1,r=0;
	for(int i=1,j=1;i<d[0];i++)
	{
		while(l<r && q[l]<=i) l++;
		while(j<d[0] && pre[j+1]-pre[i]<=pre[d[0]]-pre[j+1]+len[d[0]]+pre[i])
		{
			j++;
			while(l<=r && f[q[r]]+pre[q[r]]<=f[j]+pre[j]) r--; 
			q[++r]=j;
		}
		LL num=0;
		if(j<d[0]) num=suf[j+1]+pre[i];
		if(i<j) num=max(num,f[q[l]]+pre[q[l]]-pre[i]);
		ans=max(ans,num+f[i]);
	}
	printf("%lld",ans+1);
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值