BSOJ3068 BZOJ2500 noip模拟赛 幸福的道路 树的最长链+单调队列 或 RMQ

3068 -- 【模拟试题】幸福的道路
Description
【问题描述】
  小T与小L终于决定走在一起,他们不想浪费在一起的每一分每一秒,所以他们决定每天早上一同晨练来享受在一起的时光.
  他们画出了晨练路线的草图,眼尖的小T发现可以用树来描绘这个草图.
  他们不愿枯燥的每天从同一个地方开始他们的锻炼,所以他们准备给起点标号后顺序地从每个起点开始(第一天从起点一开始,第二天从起点二开始……). 而且他们给每条道路定上一个幸福的值.很显然他们每次出发都想走幸福值和最长的路线(即从起点到树上的某一点路径中最长的一条).
  他们不愿再经历之前的大起大落,所以决定连续几天的幸福值波动不能超过M(即一段连续的区间并且区间的最大值最小值之差不超过M).他们想知道要是这样的话他们最多能连续锻炼多少天(hint:不一定从第一天一直开始连续锻炼)?
  现在,他们把这个艰巨的任务交给你了!
Input
【输入格式】
  第一行包含两个整数N, M(M<=10^9).
  第二至第N行,每行两个数字Fi , Di, 第i行表示第i个节点的父亲是Fi,且道路的幸福值是Di.
Output
【输出格式】
  最长的连续锻炼天数
Sample Input
【样例输入】
3 2
1 1
1 3
Sample Output
【样例输出】
3
Hint
【数据范围】
  50%的数据N<=1000
  80%的数据N<=100000
  100%的数据N<=200000
题解:
First
    1. 双树形DP 
    2. 树的直径
Second
    1. 二分加线性扫描 (非常好想)
    2. 二分加ST (很好想)
    3. 双单调队列  (比较能想)

树的直径有两种求法,每次都直接DFS两次是过不了的,只有用两次树形DP,
g[x][0]表示从x的子树中,x到叶子的最长链,g[x][1]表示次长链。(用儿子更新父亲)
f[x]表示从x向上走到某个父亲,再向下的最长链。(用父亲更新儿子)
这个DP是通过两次从根出发的dfs实现的。
第二次的处理可以用单调队列或者二分+RMQ通过
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
struct node{int to,nxt,val;
}w[400005];
int cnt=0,h[400005],g[200005][2],f[200005],n,m,a[200005],q1[200005],q2[200005];
void add(int x,int y,int z)
{
	cnt++;w[cnt].to=y;w[cnt].nxt=h[x];w[cnt].val=z;h[x]=cnt;
}
void DP1(int pos,int fa)
{
	for(int i=h[pos];i;i=w[i].nxt)
	{
		int j=w[i].to;
		if(j==fa)continue;
		DP1(j,pos);
		if(w[i].val+g[j][0]>g[pos][0])
		{
			g[pos][1]=g[pos][0];
			g[pos][0]=w[i].val+g[j][0];
		}
		else g[pos][1]=max(g[pos][1],g[j][0]+w[i].val);
	}
	return ;
}
void DP2(int pos,int fa)
{
	for(int i=h[pos];i;i=w[i].nxt)
	{
		int j=w[i].to;
		if(j==fa)continue;
		f[j]=f[pos]+w[i].val;
		if(g[j][0]+w[i].val==g[pos][0])f[j]=max(f[j],g[pos][1]+w[i].val);
		else f[j]=max(f[j],g[pos][0]+w[i].val);
		DP2(j,pos);
	}
}

void solve()
{  
	int l1=1,l2=1,r1=0,r2=0,t=1,ans=0;  
  	for(int i=1;i<=n;i++)
	{  
    	while(l1<=r1&&a[i]<=a[q1[r1]]) r1--;  //small to big
    	while(l2<=r2&&a[i]>=a[q2[r2]]) r2--;  //big to small
    	q1[++r1]=i;q2[++r2]=i;  
    	while(a[q2[l2]]-a[q1[l1]]>m)
		{  
      		if(q2[l2]<=q1[l1]) t=q2[l2]+1,l2++;  
      		else t=q1[l1]+1,l1++;  
    	}   
    	ans=max(ans,i-t+1);  
  	}
  	cout<<ans;
}
int main(){
	int fa,v;
	cin>>n>>m;
	for(int i=2;i<=n;i++)
	{
		cin>>fa>>v;
		add(i,fa,v);
		add(fa,i,v);
	}
	DP1(1,0);DP2(1,0);
	for(int i=1;i<=n;i++)
	  a[i]=max(f[i],g[i][0]);
	solve();
	return 0;
}
二分+RMQ要慢一些
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
using namespace std;
struct node
{
	int to,next,val;
}w[1000005];
int h[1000005]={0},a[1000005]={0},dp1[1000005][2]={0},dp2[1000005]={0};
int maxx[200005][21]={0},minn[200005][21];
int n,m,cnt=0;
void Addarc(int x,int y,int val)
{
	cnt++;w[cnt].to=y;w[cnt].next=h[x];h[x]=cnt;w[cnt].val=val;
}
void DP1(int x,int fa)
{
	for(int i=h[x];i;i=w[i].next)
	{
		int to=w[i].to;
		if(to==fa)continue;
		DP1(to,x);
		if(dp1[to][0]+w[i].val>dp1[x][0])
		{
			dp1[x][1]=dp1[x][0];
			dp1[x][0]=dp1[to][0]+w[i].val;
		}
		else if(dp1[to][0]+w[i].val>dp1[x][1])dp1[x][1]=dp1[to][0]+w[i].val;
	}
}
void DP2(int x,int fa)
{
	for(int i=h[x];i;i=w[i].next)
	{
		int to=w[i].to;
		if(to==fa)continue;
		dp2[to]=dp2[x]+w[i].val;
		if(dp1[to][0]+w[i].val==dp1[x][0])dp2[to]=max(dp2[to],w[i].val+dp1[x][1]);
		else dp2[to]=max(dp2[to],w[i].val+dp1[x][0]);
		DP2(to,x);
	}
}
void ST()
{
	memset(minn,127,sizeof(minn));
	for(int i=1;i<=n;i++)maxx[i][0]=minn[i][0]=a[i];
	for(int j=1;j<=log2(n);j++)
	{
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			maxx[i][j]=max(maxx[i][j-1],maxx[i+(1<<(j-1))][j-1]);
			minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
		}
	}
}
int ask_mx(int l,int r)
{
	int k=log2(r-l+1);
	return max(maxx[l][k],maxx[r-(1<<k)+1][k]);
}
int ask_mi(int l,int r)
{
	int k=log2(r-l+1);
	return min(minn[l][k],minn[r-(1<<k)+1][k]);
}
void init()
{
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n;i++)
	{
		int fa,d;
		scanf("%d%d",&fa,&d);
		Addarc(fa,i,d);
		Addarc(i,fa,d);
	}
	DP1(1,0);
	DP2(1,0);
	for(int i=1;i<=n;i++)a[i]=max(dp1[i][0],dp2[i]);
	ST();
}
bool check(int len)
{
	for(int st=1;st+len-1<=n;st++)
	{
		int ed=st+len-1;
		if(ask_mx(st,ed)-ask_mi(st,ed)<=m)return 1;
	}
	return 0;
}
void Binary_Search()
{
	int l=1,r=n,ans=1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
		{
			l=mid+1;
			ans=mid;
		}
		else r=mid-1;
	}
	printf("%d\n",ans);
}
int main()
{
	init();
	Binary_Search();
return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值