[BZOJ3219]巡游

【题目描述】

Description

Tar国正在准备每年一次的巡游活动。国王将会在一个城市S里召集人群,沿着城市间的道路进行游览,最终在一个城市T里发表他每年一次的著名演讲。
Tar国有N个城市,由于国家的特殊要求,每两个城市之间存在一条唯一的简单通路。
国王希望借着这个机会视察Tar国的城市建设,因此他提出S到T的距离不能少于L条道路。
同时,国王的私人医生检查了他的身体情况后,断定国王的身体不适合做长途旅行,因此他要求S到T的距离不能多于R条道路。
另外,政府希望跟随国王的人民沿途不仅能看到城市风景,还能看到城市外的美丽乡村。因此每条道路定义了一个魅力值Ci,一条路径的魅力值定义为这条路径的中位数。更详细的说法是这样的:
将路径上所有边的魅力值排序,得到序列{Ai}。假设i=2k+c(0<=c<=1),中位数就是A(k+1)。
你的任务就是求出魅力值最大的路径,并输出这个魅力值。

Input

第一行是三个整数N,L,R,表示Tar国的城市个数、路径的最小和最大长度。
接下来N-1行,每行3个整数Ai,Bi,Ci,表示有一条连接Ai和Bi且魅力值Ci的道路。

Output

 
仅一行,表示最大的魅力值。如果不存在这样的路径,输出-1。

Sample Input

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

Sample Output

7

HINT

对于100%的数据:N<=100000,1<=L<=R<=N-1,1<=Ci<=1000000000

【解题思路】

首先用二分答案把题目转化成在树中能否找到一条中位数>=mid的合法路径的判定性问题。

把<mid的边权标记-1,其他的标记1,则对于一条标记和>=0的合法路径,有中位数>=mid

用树分治解决,记录每个点的深度(到重心的距离)和到重心路径的标记和

O(Nlog³N)

因为与某个节点可以构成合法路径的节点是连续的,所以可以用线段树快速寻找最优路径

线段树清空记得用标记


O(Nlog²N)

对于已处理子树,每个深度我们只需要记录一个最优节点的信息best[]

还是利用“连续”性质,bfs遍历新子树并用单调队列O(1)寻找最优匹配点

【优化】

对于每次二分的每个重心计算,我们都要清空一次best[]

所以为了节省时间,在二分求答案之前,先进行一次树分治,一是把子树按最大深度从小到大排序,使best[]的有效范围变成逐渐扩大的,二是将重心顺序存下免去后续冗余计算

对于子树大小<=l的重心没必要处理

【呆马】

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<iostream>
using namespace std;
const int N=1e5+1,inf=1e9;
int n,m,cnt,x,y,z,i,l,r,L,R,mid,V,ans,dhe,dta,qhe,qta,ROOT,mxd,a[N],fi[N],f[N],siz[N],root[N],son[N],v[N],bes[N];
int to[N<<1],ne[N<<1],val[N<<1],fa[N],dep[N],d[N],qd[N],qv[N];
bool vis[N];
bool cmp(int x,int y){return dep[x]<dep[y];}
void add(int x,int y,int z)
{
	to[++cnt]=y; ne[cnt]=fi[x]; val[cnt]=z; fi[x]=cnt;
	to[++cnt]=x; ne[cnt]=fi[y]; val[cnt]=z; fi[y]=cnt;
}

void getroot(int x,int fa,int num)
{
	int y,i;
	f[x]=0,siz[x]=1;
	for (y=to[i=fi[x]];i;y=to[i=ne[i]])
		if (!vis[y] && y!=fa)
		{
			getroot(y,x,num);
			f[x]=max(f[x],siz[y]);
			siz[x]+=siz[y];
		}
	f[x]=max(f[x],num-siz[x]);
	if (f[x]<f[ROOT]) ROOT=x;
}

int dfs(int x,int fa,int dep)
{
	int Max=0;
	if (dep==r) return r;
	for (int i=fi[x];i;i=ne[i])
		if (!vis[to[i]] && to[i]!=fa)
		{
			Max=max(Max,dfs(to[i],x,dep+1));
			if (Max==r) return r;
		}
	return dep;
}

void part(int x)
{
	int num=0;
	vis[x]=1;
	for (int i=fi[x];i;i=ne[i])
		if (!vis[to[i]]) dep[son[++num]=to[i]]=dfs(to[i],x,1),v[son[num]]=val[i];
	sort(son+1,son+num+1,cmp);
	for (int i=fi[x],t=0;i;i=ne[i])
		if (!vis[to[i]]) to[i]=son[++t],val[i]=v[son[t]];
	for (int i=fi[x];i;i=ne[i])
		if (!vis[to[i]] && siz[to[i]]>=l)
		{
			ROOT=0;
			getroot(to[i],x,siz[to[i]]);
			part(root[++m]=ROOT);
		}
}

bool bfs()
{
	int x,y;
	for (;dhe<=dta;dhe++)
	{
		x=d[dhe];
		y=dep[dhe];
		if (dhe!=1 && y!=dep[dhe-1])
		{
			if (y+qd[qhe]>r) qhe++;
			int t=l-y;
			if (t>0) t=bes[t];
			if (t>-inf)
			{
				for (;qhe<=qta && t>=qv[qta];qta--);
				qd[++qta]=l-y;
				qv[qta]=t;
			}
		}
		if ((qhe<=qta && v[x]+qv[qhe]>=0) || (y>=l && v[x]>=0)) return 1;
		if (y<r)
		{
			for (int i=fi[x];i;i=ne[i])
				if (!vis[to[i]] && to[i]!=fa[x])
				{
					d[++dta]=to[i];
					dep[dta]=y+1;
					if (val[i]<V) v[d[dta]]=v[x]-1;
					else v[d[dta]]=v[x]+1;
					fa[d[dta]]=x;
				}
		}
	}
	return 0;
}

bool find(int x)
{
	for (int i=1;i<=mxd;i++) bes[i]=-inf;
	vis[x]=1;
	for (int i=fi[x];i;i=ne[i])
		if (!vis[to[i]])
		{
			qhe=1,qta=0;
			for (int j=mxd;j>=l-1;j--)
				if (bes[j]>-inf)
				{
					for (;qhe<=qta && qv[qta]<=bes[j];qta--);
					qd[++qta]=j;
					qv[qta]=bes[j];
				}
			dep[dhe=dta=1]=1;
			d[1]=to[i];
			if (val[i]<V) v[d[1]]=-1;
			else v[d[1]]=1;
			fa[d[1]]=x;
			if (bfs()) return 1;
			mxd=max(mxd,dep[dta]);
			for (int j=1;j<=dta;j++) bes[dep[j]]=max(bes[dep[j]],v[d[j]]);
		}
	return 0;
}

int main()
{
		scanf("%d%d%d\n",&n,&l,&r);
		for (i=1;i<n;i++)
		{
			scanf("%d%d%d\n",&x,&y,&z);
			a[i]=z;
			add(x,y,z);
		}
		sort(a+1,a+n);
		R=unique(a+1,a+n)-(a+1);
		f[0]=inf;
		getroot(1,0,n);
		part(root[m=1]=ROOT);
		ans=-1;
		for (i=1;i<=n;i++) bes[i]=-inf;
		for (L=1;L<=R;)
		{
			for (i=1;i<=n;i++) vis[i]=0;
			mid=(L+R)>>1;
			V=a[mid];
			bool bo=0;
			for (i=1;i<=m;i++)
				if (find(root[i]))
				{
					bo=1;
					break;
				}
			if (bo) L=mid+1,ans=V;
			else R=mid-1;
		}
		printf("%d",ans);
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值