两种解法-树形dp+二分+单调队列(或RMQ)-hdu-4123-Bob’s Race
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4123
题目大意:
给一棵树,n个节点,每条边有个权值,从每个点i出发有个不经过自己走过的点的最远距离Ma[i],有m个询问,每个询问有个q,求最大的连续节点区间长度ans,使得该区间内最大的M[i]和最小的M[j]之差不超过q。
解题思路一:
这套题目好卡时间。
树形dp+二分+单调队列,几个基本的知识点杂糅在一起。
先用树形dp求出从任意一点i出发的Ma[i].两遍dfs,第一遍求出每个节点为根到儿子方向的最大距离并记录最大距离得到的直接儿子,和与最大距离路径没有重边的次大距离。第二遍求出每个点的最远距离Ma[i]要么从儿子方向得到,要么从父亲方向得到。
然后对于每个询问q,二分区间长度mid,用单调队列求出区间长度为mid的最大Ma[i]和最小Ma[j]的差值的最小值pp。保存起来,当已经求得了该区间长度的值pp时,直接返回pp.
与q比较,因为是存在性问题,每次都把该区间长度的最小的值pp来比较。这样一区间长度为标准,避免了,每次查询相同区间长度只是q不同的情况。不然会超时。
代码:
#include<iostream>#include<cmath>#include<cstdio>#include<cstdlib>#include<string>#include<cstring>#include<algorithm>#include<vector>#include<map>#include<set>#include<stack>#include<list>#include<queue>#include<ctime>#define eps 1e-6#define INF 0x3f3f3f3f#define PI acos(-1.0)#define ll __int64#define lson l,m,(rt<<1)#define rson m+1,r,(rt<<1)|1#pragma comment(linker, "/STACK:1024000000,1024000000")using namespace std;#define Maxn 55000int dp[Maxn][2],path[Maxn]; //每个点到儿子节点的最大和次大距离,最大距离经过直接儿子int Ma[Maxn],q1[Maxn],q2[Maxn],cnt;int n,m;struct Edge{ int v,len; struct Edge * next;}*head[Maxn<<1],edge[Maxn<<1];void add(int a,int b,int len){ ++cnt; edge[cnt].v=b,edge[cnt].len=len; edge[cnt].next=head[a]; head[a]=&edge[cnt];}void dfs1(int cur,int pa) //第一遍dfs,求出最大距离和次大距离以及路径{ struct Edge * p=head[cur]; while(p) { int v=p->v,len=p->len; if(v!=pa) { dfs1(v,cur); if(len+dp[v][0]>dp[cur][0]) //更新最大 { dp[cur][1]=dp[cur][0];//更新次大 dp[cur][0]=len+dp[v][0]; path[cur]=v; } else if(len+dp[v][0]>dp[cur][1]) //更新次大 dp[cur][1]=len+dp[v][0]; } p=p->next; }}void dfs2(int cur,int pa,int tmp){ Ma[cur]=max(dp[cur][0],tmp);//求出最远距离,要么从儿子方向,要么从父亲方向 struct Edge * p=head[cur]; while(p) { int v=p->v,len=p->len; if(v!=pa) { if(v==path[cur])//儿子方向最大值的直接儿子,从父亲中只能选次大 dfs2(v,cur,len+max(tmp,dp[cur][1])); else dfs2(v,cur,len+max(tmp,dp[cur][0])); } p=p->next; }}int rmq1[20][Maxn],rmq2[20][Maxn];int logg[Maxn];void rmq_init(){ for(int i=1;i<=n;i++) rmq1[0][i]=rmq2[0][i]=Ma[i]; for(int i=1;i<=logg[n];i++) //枚举区间长度,递增 { for(int j=1;j<=n;j++) { rmq1[i][j]=max(rmq1[i-1][j],rmq1[i-1][j+(1<<(i-1))]); rmq2[i][j]=min(rmq2[i-1][j],rmq2[i-1][j+(1<<(i-1))]); } }}int rmq_min(int l,int r){ int tmp=logg[r-l+1]; int a=max(rmq1[tmp][l],rmq1[tmp][r-(1<<tmp)+1]); int b=min(rmq2[tmp][l],rmq2[tmp][r-(1<<tmp)+1]); return a-b;}int main(){ logg[0]=-1; for(int i=1;i<=50000;i++) logg[i]=logg[i>>1]+1; //logg[i]表示int logg2(i) while(scanf("%d%d",&n,&m)&&n+m) { cnt=0; memset(head,NULL,sizeof(head)); int a,b,c; for(int i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } memset(dp,0,sizeof(dp)); dfs1(1,1);//求出以任意点i为根儿子方向的最大和次大距离 dfs2(1,1,0); rmq_init(); for(int i=1;i<=m;i++) { int q; scanf("%d",&q); int l=1,r=1,ans=0; while(r<=n&&l<=n) { if(rmq_min(l,r)<=q) //找到以l开始的能满足的最大区间长度 { ans=max(ans,r-l+1); r++; } else //以i+1点开始的区间长度必须大于等于前面的满足区间长度才可以,所以r指针可以不动 l++; } printf("%d\n",ans); } } return 0;}