bzoj3242: [Noi2013]快餐店

10 篇文章 0 订阅
9 篇文章 0 订阅

——来自一个失去梦想的咸鱼miaom

考虑海蜇基环树的一般套路,在确定快餐店位置的情况下,最优解中环上必有一条边是废的。思考枚举这条边,我们需要在最快的时间求剩余部分的直径。当前答案就是直径/2,证明非常简单,就离快餐店最远的点一定是直径端点。这个东西可以通过双指针单调队列维护,达到O(n)复杂度。然后我就失去了梦想,直接线段树水过了。

线段树做法如下:先搞出那个环,重复一遍变成序列问题,询问一个区间的直径。有一种情况直径不在环上,可以预处理。另一种情况至于环上点子树最深的点有关,使用线段树维护那个点的深度±在环中的位置区间合并,具体见代码~

至于怎么搞环,不是重点,一波dfs即可(其实拓扑排序也可以)。

#include<bits/stdc++.h>
#define N 200005
#define ll long long
using namespace std;
ll n,x,y,z,m;
ll fst[N],to[N*2],nxt[N*2],len[N],l;
ll vis[N],fa[N],flag,flag1,flag2,fl[N];
ll a[N];ll b[N],d[N],M1[N],Mx[N],dep[N],vl[N],vr[N],Ans,ly;
void link(ll x,ll y,ll z)
{
	to[++l]=y;len[l]=z;nxt[l]=fst[x];fst[x]=l;
	to[++l]=x;len[l]=z;nxt[l]=fst[y];fst[y]=l;
}
void dfs(ll x)
{
	//cout<<x<<endl;
	vis[x]=1;
	for (ll i=fst[x];i;i=nxt[i])
		if (to[i]!=fa[x])
		{
			if (vis[to[i]])
			{
				if (!flag)
				flag=x,flag1=to[i],flag2=len[i];
			}
			else
			{
				dep[to[i]]=dep[x]+len[i];
				fa[to[i]]=x;
				dfs(to[i]);
				//fl[x]+=fl[to[i]];
			}
		}
}
void Dfs(ll x)
{
	Mx[x]=0;
	M1[x]=0;
	for (ll i=fst[x];i;i=nxt[i])
		if (to[i]!=fa[x]&&fl[to[i]]==0)
		{
			fa[to[i]]=x;
			Dfs(to[i]);
			Mx[x]=max(Mx[x],max(Mx[to[i]],M1[x]+M1[to[i]]+len[i]));
			M1[x]=max(M1[x],M1[to[i]]+len[i]);
		}
}
struct T
{
	ll l,r,a;
}nd[N*8],now;
T operator+(T a,T b)
{
	return (T){max(a.l,b.l),max(a.r,b.r),max(max(a.a,b.a),a.l+b.r)};
}
void build(ll k,ll l,ll r)
{
	if (l==r)
	{
		nd[k]=(T){vl[l],vr[l],0};
		return;
	}
	ll mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	nd[k]=nd[k<<1]+nd[k<<1|1];
}
void qry(ll k,ll l,ll r,ll x,ll y)
{
	if (x<=l&&r<=y)
	{
		if (now.a==-1) now=nd[k];
		else now=now+nd[k];
		return;
	}
	ll mid=l+r>>1;
	if (x<=mid) qry(k<<1,l,mid,x,y);
	if (y>mid) qry(k<<1|1,mid+1,r,x,y);
}
int main()
{
	scanf("%lld",&n);
	for (ll i=1;i<=n;i++)
	{
		scanf("%lld%lld%lld",&x,&y,&z);
		link(x,y,z);
	}
	dfs(1);
	for (;flag!=flag1;flag=fa[flag])
		fl[a[++m]=flag]=1,b[m]=dep[flag]-dep[fa[flag]];
		
	fl[a[++m]=flag1]=1;b[m]=flag2;
	for (ll i=1;i<=m;i++)
		fa[a[i]]=0,Dfs(a[i]),ly=max(ly,Mx[a[i]]);
	
	for (ll i=1;i<=m;i++)
		a[i+m]=a[i],b[i+m]=b[i];
	
	for (ll i=1;i<=2*m;i++)
	{
		d[i]=d[i-1]+b[i-1];
		vl[i]=M1[a[i]]-d[i]; 
		vr[i]=M1[a[i]]+d[i];
	}
	build(1,1,2*m);
	Ans=100000000000000L;
	for (ll i=1;i<=m;i++)
	{
		now=(T){0,0,-1};
		qry(1,1,2*m,i,i+m-1);
		Ans=min(Ans,max(now.a,ly));
	}
	
	printf("%lld.%d\n",Ans/2,Ans&1?5:0);
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值