树形DP+RMQ+尺取法 hdu4123

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4123

参考博客:两种解法-树形dp+二分+单调队列(或RMQ)-hdu-4123-Bob’s Race - cc_again的专栏 - CSDN博客  https://blog.csdn.net/cc_again/article/details/12011757

题意:给出一棵有n个点的树,每条树边都有一个权值,对于每一个点,它都有一个编号,我们取它到其他点的最大距离记录下来,现在q个询问,每个询问会给出一个k,我们要找到最长的一段点的编号是连续的区间,满足这段区间里面的点到其他点的最大距离  的最大值减去这段区间里面点到其他区间的最大值 里面的最小值 的差值小于等于k,输出这段区间的长度。

思路:首先我们要求出这棵树上面的每一个点到其他点的最大距离,所以我们进行两次记忆化搜索(两次DFS),第一次DFS我们挑一个点为根节点(假设为1)往下搜索每一颗子树的根节点到它的儿子节点距离的最大值和次大值(次大值是为了等下求一个点到它的非子树节点的最大距离),然后进行第二次DFS(还是从1开始),向下搜索找每个点到它的非子树节点的最大距离。这样我们最后把两个最大值比较就可以得到这颗树上的每一个点到其他点的最大距离。

每一次我们要查询的就是一段连续区间里面的最大值减去最小值,看这个值是否小于k,因为查询次数很多,所以我们可以用RMQ来进行预处理,先计算出所有区间里面的最大值和最小值,最后用尺取法来枚举区间边界,同时用预处理好了的值进行判断,来找出最大区间长度。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque> 
using namespace std;
typedef long long LL;
#define eps 1e-8
#define INF 0x3f3f3f3f
#define maxn 50005
int maxx[maxn][20],minn[maxn][20],mx1[maxn],mx2[maxn],f[maxn];
//maxx存区间最大值,minn...,mx1[i]存点i到其他点的最大距离,mx2[i]存次大距离
//f[i]存点i到非子树节点的最大距离 
int n,m,t,cnt,ans; 
int head[maxn];
bool vis[maxn];
struct node{
    int v,w,next;
}edge[maxn<<1];
void init(){
    memset(head,-1,sizeof(head));
    cnt=0;
    memset(mx1,0,sizeof(mx1));
    memset(mx2,0,sizeof(mx2));
    memset(f,0,sizeof(f));
}
void add(int u,int v,int w){
    edge[++cnt].v=v;
    edge[cnt].next=head[u];
    edge[cnt].w=w;
    head[u]=cnt;
}
void DFS(int u){//找点u到子树节点里面的最大距离和次大距离 
    vis[u]=true;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        int w=edge[i].w;
        if(!vis[v]){
            DFS(v);
            mx2[u]=max(mx1[v]+w,mx2[u]);
            if(mx2[u]>mx1[u]) swap(mx2[u],mx1[u]);
        }
    }
    
}
void DFS1(int u){//找点u到非子树节点的最大距离 
    vis[u]=true;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        int w=edge[i].w;
        if(!vis[v]){
            f[v]=max(f[v],f[u]+w);
            if(mx1[u]==w+mx1[v]) 
            f[v]=max(f[v],w+mx2[u]);
            else
            f[v]=max(f[v],w+mx1[u]);
            DFS1(v);
        }
    }
}
void RMQ_init(){//RMQ预处理 
    for(int i=1;i<=n;i++)
    maxx[i][0]=minn[i][0]=max(f[i],mx1[i]);
    for(int j=1;(1<<j)<=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 cal(int l,int r){//返回区间[l,r]的最大值减最小值的值 
    int k=0;
    while((1<<(k+1))<=r-l+1)
    k++;
    return max(maxx[l][k],maxx[r-(1<<k)+1][k])-min(minn[l][k],minn[r-(1<<k)+1][k]);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF){
        if(!n||!m)
        break;
        init();
        int u,v,w;
        for(int i=1;i<=n-1;i++){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
        }
        memset(vis,false,sizeof(vis));
        DFS(1);
        memset(vis,false,sizeof(vis));
        DFS1(1);
        RMQ_init();
        int k;
        while(m--){
            scanf("%d",&k);
            int l=1,r=1;
            ans=0;
            while((l<n||r<n)&&l<=r){//尺取法枚举区间 
                while(r<n&&cal(l,r)<=k){
                    ans=max(ans,r-l+1);
                    r++;
                }
                if(r==n&&cal(l,r)<=k){
                    ans=max(ans,r-l+1);
                    break;
                }
                l++;
            }
            printf("%d
",ans);
        }
        
    }
    return 0;
}
原文地址:https://www.cnblogs.com/6262369sss/p/10008777.html