HDU 4123-Bob’s Race //树形dp+rmq+尺取

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=4123

题意

题意大概就是给你一棵树,有 n n n 个顶点、 n − 1 n-1 n1 条边,每条边有一个权值,现在让你求出每个点距离其他点的最长距离,然后有 m m m 组询问,每次询问给一个数 q q q ,每次询问在顶点 [1,n] 这个范围内,其中连续的一段(顶点编号连续) 最长距离 的最大值和最小值之差,在不大于 q q q 的情况下,最大化这个区间。

解析

(1)第一步
要求出每个点距离其他点的最长距离,有原题:Hdu 2196
具体做法就是,先固定根(一般选 1 1 1 号点),第一遍 dfs 维护每个点的最长距离和次长距离,第二遍 dfs 求每个点经过其父亲路径的最长距离。
原因:因为固定根了,所以最长距离一定是由儿子得来的,而我们要求每个点距离其他点的最长距离无非就是 “由儿子得来的最长距离” 和 “由经过父亲得来的最长距离” 两者最大的一者。
而求“由经过父亲得来的最长距离”,我们需要求每个点由儿子得来的次长距离。例如:
在这里插入图片描述
假如我们要求 2 2 2 号点的最长距离,除了它儿子过来的最大值(蓝线),还有就是其父亲的路径(红线部分),蓝线部分很好求,而红线部分其中就包含以 1 1 1 号点为根时,距离 1 1 1 号点的次长距离(就是 d i s ( 1 , 3 ) dis(1,3) dis(1,3) ),还有 d i s ( 1 , 2 ) dis(1,2) dis(1,2)
具体代码为:

//dp[u][0]:定根时,距离u点的最长距离
//dp[u][1]:定根时,距离u点的次长距离
//dp[u][2]:定根时,经过u点父亲距离u点的最长距离
void dfs(int u,int fa){//求dp[u][0]和dp[u][1]
    dp[u][0]=dp[u][1]=0;
    for(int i=0;i<(int)g[u].size();i++){
        int v=g[u][i].ft,w=g[u][i].sd;
        if(v==fa)continue;
        dfs(v,u);
        if(dp[u][0]<dp[v][0]+w){//更新最长和次长
            dp[u][1]=dp[u][0],dp[u][0]=dp[v][0]+w;
        }
        else if(dp[u][1]<dp[v][0]+w){//更新次长
            dp[u][1]=dp[v][0]+w;
        }
    }
}
void _dfs(int u,int fa){//求dp[u][2]
    for(int i=0;i<(int)g[u].size();i++){
        int v=g[u][i].ft,w=g[u][i].sd;
        if(v==fa)continue;
        //当前点在父节点的最长路上,要用到父节点的次长路
        if(dp[u][0]==dp[v][0]+w){
            dp[v][2]=max(dp[u][1],dp[u][2])+w;
        }
        //当前点不在父节点的最长路上,要用到父节点的最长路
        else {
            dp[v][2]=max(dp[u][2],dp[u][0])+w;
        }
        _dfs(v,u);
    }
}

(2)第二步
求区间最小值,和最大值,可以用线段树、ST表。线段树代码量贼多,直接用ST表了。求解的目的是因为我们要使区间最大最小差值在满足 不大于 询问值 的情况下,最大化,所以尺取的前提是知道区间最大最小的差值。所以就依 1 − n 1-n 1n 点的最长距离建ST表。

int Log2(int x){// 求 log2(x)
    int bit=1,ans=0;
    while(bit<=x){
        bit<<=1;
        ans++;
    }
    return ans-1;
}
struct RMQ{//求区间最大最小值
    ll ma[maxn][21],mi[maxn][21];
    void init(){
        for(int j=1;j<=20;j++){
            for(int i=1;i<=n;i++){
                ma[i][j]=0;
                mi[i][j]=inf;
            }
        }
        //最后每个点的最长距离是max(dp[u][0],dp[u][2]),我提前把它更新在了dp[u][0]
        for(int i=1;i<=n;i++)ma[i][0]=mi[i][0]=dp[i][0];
        for(int j=1;j<=20;j++){
            for(int i=(1<<j);i<=n;i++){
                ma[i][j]=max(ma[i][j-1],ma[i-(1<<(j-1))][j-1]);
                mi[i][j]=min(mi[i][j-1],mi[i-(1<<(j-1))][j-1]);
            }
        }
    }
    ll query_ca(int l,int r){//求差值
        int x=Log2(r-l+1);//此处用库函数会超时,贼坑
        ll mma=max(ma[r][x],ma[l+(1<<x)-1][x]);
        ll mmi=min(mi[r][x],mi[l+(1<<x)-1][x]);
        return mma-mmi;
    }
}rmq;

(3)第三步
尺取法擅长求满足一定条件的最大区间,需要用两个指针 l , r l,r l,r,不断移动 l , r l,r l,r 中间更新最大区间,直至 l , r l,r l,r 超出范围。

void solve(){//每组询问
    int q;scanf("%d",&q);
    int l=1,r=1,ans=0;
    while(l<=n&&r<=n){
        while(r<=n&&rmq.query_ca(l,r)<=q){
            r++;
        }
        if(r-l>ans){//更新最优解
            ans=r-l;
        }
        l++;
    }
    printf("%d\n",ans);
}

完整代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#define ft first
#define sd second
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int maxn=5e4+7;
const int inf=0x3f3f3f3f;
int n,m;
ll dp[maxn][3];
vector<P> g[maxn];
int Log2(int x){// 求 log2(x)
    int bit=1,ans=0;
    while(bit<=x){
        bit<<=1;
        ans++;
    }
    return ans-1;
}
struct RMQ{//求区间最大最小值
    ll ma[maxn][21],mi[maxn][21];
    void init(){
        for(int j=1;j<=20;j++){
            for(int i=1;i<=n;i++){
                ma[i][j]=0;
                mi[i][j]=inf;
            }
        }
        //最后每个点的最长距离是max(dp[u][0],dp[u][2]),我提前把它更新在了dp[u][0]
        for(int i=1;i<=n;i++)ma[i][0]=mi[i][0]=dp[i][0];
        for(int j=1;j<=20;j++){
            for(int i=(1<<j);i<=n;i++){
                ma[i][j]=max(ma[i][j-1],ma[i-(1<<(j-1))][j-1]);
                mi[i][j]=min(mi[i][j-1],mi[i-(1<<(j-1))][j-1]);
            }
        }
    }
    ll query_ca(int l,int r){//求差值
        int x=Log2(r-l+1);//此处用库函数会超时,贼坑
        ll mma=max(ma[r][x],ma[l+(1<<x)-1][x]);
        ll mmi=min(mi[r][x],mi[l+(1<<x)-1][x]);
        return mma-mmi;
    }
}rmq;
//dp[u][0]:定根时,距离u点的最长距离
//dp[u][1]:定根时,距离u点的次长距离
//dp[u][2]:定根时,经过u点父亲距离u点的最长距离
void dfs(int u,int fa){//求dp[u][0]和dp[u][1]
    dp[u][0]=dp[u][1]=0;
    for(int i=0;i<(int)g[u].size();i++){
        int v=g[u][i].ft,w=g[u][i].sd;
        if(v==fa)continue;
        dfs(v,u);
        if(dp[u][0]<dp[v][0]+w){//更新最长和次长
            dp[u][1]=dp[u][0],dp[u][0]=dp[v][0]+w;
        }
        else if(dp[u][1]<dp[v][0]+w){//更新次长
            dp[u][1]=dp[v][0]+w;
        }
    }
}
void _dfs(int u,int fa){//求dp[u][2]
    for(int i=0;i<(int)g[u].size();i++){
        int v=g[u][i].ft,w=g[u][i].sd;
        if(v==fa)continue;
        //当前点在父节点的最长路上,要用到父节点的次长路
        if(dp[u][0]==dp[v][0]+w){
            dp[v][2]=max(dp[u][1],dp[u][2])+w;
        }
        //当前点不在父节点的最长路上,要用到父节点的最长路
        else {
            dp[v][2]=max(dp[u][2],dp[u][0])+w;
        }
        _dfs(v,u);
    }
}
void solve(){//每组询问
    int q;scanf("%d",&q);
    int l=1,r=1,ans=0;
    while(l<=n&&r<=n){
        while(r<=n&&rmq.query_ca(l,r)<=q){
            r++;
        }
        if(r-l>ans){//更新最优解
            ans=r-l;
        }
        l++;
    }
    printf("%d\n",ans);
}
int main() {
    while(~scanf("%d%d",&n,&m)){
        if(!n&&!m)break;
        for(int i=1;i<=n;i++)g[i].clear();
        for(int i=1;i<=n-1;i++){
            int u,w,v;scanf("%d%d%d",&u,&v,&w);
            g[u].pb(P(v,w));
            g[v].pb(P(u,w));
        }
        dfs(1,0);
        _dfs(1,0);
        //最长距离统一存在dp[i][0]
        for(int i=1;i<=n;i++){
            dp[i][0]=max(dp[i][0],dp[i][2]);
        }
        rmq.init();
        while(m--){
            solve();
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值