2015 noip 运输计划(lca +二分+树上差分)

最长的路径最短,可以考虑二分ans。验证ans成不成立。

如果能找到一条边,删去这边后,所有大于ans的路径都小于ans 了,那么成了。

这条边一定要被所有大于ans的路径经过,并且是所有满足这个条件的边的边权最大的那条。

计算树上一条边被经过次数差分啊! 起点+1,终点+1,lca-2.(lca可以用倍增,tarjian,树剖,个人喜好吧)。

对于一个点,以它为根的子树的权值和(权值和包括它自己啊)就是它到它父亲这条边被经过的次数。

把满足被所有大于ans的路径经过边权最大的边找到。枚举每个大于ans的路径。如果有一条路径长度-这条边的边权 > ans,

说明ans不成立。

优化:
1.二分的左边界,最长的路径-最大边权。右边界为最长的路径。

2.提前预处理出每个运输计划起点和终点的lca.

//在算大于ans的路径数时,cnt一定要清0啊,我就是这个没清,吓的我以为我边都建错了。。。。调了好久。。。。改清的一定要清干净啊!

复杂度应该是log最大边权 m的。

#include<bits/stdc++.h>
using namespace std;
const int N = 300005;
int n,m, ma,  dis[N],  dis1[N], cha[N], zong[N], cnt, k, maa, ql[N], qr[N], lc[N];
int dep[N], top[N], son[N], siz[N],fa[N]; 
int tp, nex[2*N], tov[2*N], tow[2*N], h[N];
void read(int &x)
{
   x = 0; int f = 0 ; char c = getchar();
   while(c < '0' || c > '9')
   {
   	if(c == '-') f = 1; c = getchar();
	} 
    while(c >= '0' && c <= '9')
    {
  	x = x * 10 + c - '0'; c = getchar(); 
    }
  if(f) x = -x;
} 
void add(int x,int y,int w)
{
   tp++;
  tov[tp] = y;  tow[tp] = w;  nex[tp] = h[x];  h[x] = tp;
}
void dfs(int x,int u)
{
	fa[x]  = u; siz[x] = 1;
	for(int i  = h[x]; i; i = nex[i])
	{
		int v = tov[i];
		if(v == fa[x]) continue;
		dep[v] = dep[x] + 1;
		dfs(v,x);
		siz[x] += siz[v];
		if(siz[v] > siz[son[x]]) son[x] = v;
	}
}
void dfs1(int x,int u)
{
	top[x] = u;
	if(son[x]) dfs1(son[x],u);
	for(int i = h[x]; i; i =  nex[i])
	{
		int v = tov[i];
		if(v == son[x] || v == fa[x]) continue;
		dfs1(v,v); 
	}
}
int lca(int x,int u,int i)
{
	while(top[x] != top[u])
	{
		if(dep[top[x]] > dep[top[u]]) swap(x,u);  u = fa[top[u]];
	}
	return dep[x] > dep[u] ? u : x;
}
void dfs2(int x)
{
	zong[x] = cha[x];
	for(int i = h[x]; i; i = nex[i])
	{
		int v = tov[i];
		if(v == fa[x]) continue;
		dfs2(v);
		zong[x] += zong[v];
	    if(zong[v] == cnt){k = max(k,tow[i]);} 
	}
}
void dfs3(int x)
{
	for(int i = h[x]; i; i = nex[i])
	{
		int v = tov[i];
		if(v == fa[x]) continue;
		dis1[v] = dis1[x] + tow[i];
		dfs3(v);
	}
}
int check(int x)
{
	memset(zong,0,sizeof(zong));
   memset(cha,0,sizeof(cha)); k = 0;cnt = 0;
   for(int i = 1; i <= m; i++)
   if(dis[i] > x)
   {
   	cnt++;  cha[ql[i]] ++; cha[qr[i]] ++ ; cha[lc[i]] -= 2;
   }
   dfs2(1); 
  for(int i = 1; i <= m; i++)
   if(dis[i] - k > x) return 0;
  return 1;     
}
int main()
{
	read(n);read(m);
	for(int i = 1; i < n; i++)
	{
		int x,y,w;
		read(x);read(y);read(w); ma = max(w,ma);
		add(x,y,w);add(y,x,w);
	}
	dfs(1,0);  dfs1(1,0); dfs3(1);
	for(int i = 1; i <= m; i++)
	{
		read(ql[i]);read(qr[i]);
		lc[i] = lca(ql[i],qr[i],i);
		dis[i] = dis1[ql[i]] + dis1[qr[i]] - 2 * dis1[lc[i]];
		maa = max(maa, dis[i]);
	}	
	int l = maa - ma , r = maa , ans = 0;
	while(l <= r)
	{
		int mid = (l + r) / 2;
		if(check(mid))   
		{ans = mid;   r  = mid - 1;}
		else l = mid + 1;
	}
	printf("%d",ans);
	return 0;
 } 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值