POJ3162 Walking Race(树形DP+尺取法+单调队列)

时间:2023-03-08 16:39:13

题目大概是给一棵n个结点边带权的树,记结点i到其他结点最远距离为d[i],问d数组构成的这个序列中满足其中最大值与最小值的差不超过m的连续子序列最长是多长。

各个结点到其他结点的最远距离可以用树形DP解决,HDU2196

而那个最长的连续子序列可以用单调队列求。。搞了挺久看了解法体会了下。。简单来说就是尺取法,用两个指针[i,j]表示区间,j不停+1往前移动,然后用两个单调队列分别同时更新区间最小值和最大值,再看两个队列队首的最值差是否大于m,是的话出队并调整i值,最后用j-i+1更新答案。

当然尺取法+RMQ也是OK的。

 #include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF (1LL<<60)
#define MAXN 1000111
struct Edge{
int v,w,next;
}edge[MAXN];
int NE,head[MAXN];
void addEdge(int u,int v,int w){
edge[NE].v=v; edge[NE].w=w; edge[NE].next=head[u];
head[u]=NE++;
}
int idx[MAXN];
long long d[][MAXN];
void dp0(int u){
for(int i=head[u]; i!=-; i=edge[i].next){
int v=edge[i].v;
dp0(v);
if(d[][u]<=d[][v]+edge[i].w){
d[][u]=d[][u];
d[][u]=d[][v]+edge[i].w;
idx[u]=v;
}else if(d[][u]<d[][v]+edge[i].w){
d[][u]=d[][v]+edge[i].w;
}else if(d[][u]<d[][v]+edge[i].w){
d[][u]=d[][v]+edge[i].w;
}
}
}
void dp1(int u){
for(int i=head[u]; i!=-; i=edge[i].next){
int v=edge[i].v;
if(idx[u]==v) d[][v]=max(d[][u],d[][u])+edge[i].w;
else d[][v]=max(d[][u],d[][u])+edge[i].w;
dp1(v);
}
}
struct Que{
int que[MAXN],front,rear;
bool isEmpty(){
return front==rear;
}
int getFront(){
return que[front];
}
int getRear(){
return que[rear-];
}
void push(int a){
que[rear++]=a;
}
}mxq,mmq;
int main(){
memset(head,-,sizeof(head));
int n,m,a,b;
scanf("%d%d",&n,&m);
for(int i=; i<=n; ++i){
scanf("%d%d",&a,&b);
addEdge(a,i,b);
}
dp0();
dp1();
int res=;
for(int i=,j=; i<=n; ++i){
d[][i]=max(d[][i],d[][i]);
while(!mxq.isEmpty() && d[][mxq.getRear()]<d[][i]) --mxq.rear;
mxq.push(i);
while(!mmq.isEmpty() && d[][mmq.getRear()]>d[][i]) --mmq.rear;
mmq.push(i);
while(d[][mxq.getFront()]-d[][mmq.getFront()]>m){
if(mxq.getFront()<mmq.getFront()) j=mxq.getFront()+,++mxq.front;
else j=mmq.getFront()+,++mmq.front;
}
res=max(res,i-j+);
}
printf("%d",res);
return ;
}