Codeforces Round 867 (Div. 3)F题解

Gardening Friends

在这里插入图片描述在这里插入图片描述

问题建模

给定一棵无向树,一棵树有一个价值,其价值为根节点到其余节点的距离最大值乘以k,现在有一个操作,可以将根节点由1转为其他节点,花费成本为根节点到该节点的路径边数乘以c,问该树可获得的最大价值-花费的最大值为多少

思路分析

1.分析所求

对于所求,我们需要知道每一个点为根时,到其余节点的最大距离,以及该点到节点1的边数。对于任意点到节点1的边数可以由根节点开始做一次dfs得到,而任意点为根到其余节点的最大距离有三种方法可得。

2.方法1利用树的直径的性质求任意节点到其余节点的最大距离

树的直径为:树上任意两节点之间最长的简单路径。其性质为:在一棵树上,从任意节点y开始进行一次 DFS,到达的距离其最远的节点z必为直径的一端。证明可以通过分三种情况反证,每次比较起始点所找路基与最近直径端点比较可得。

  1. 起始点在该直径上

  2. 起始点不在直径上但路径有重合

  3. 起始点不在该直径上,且所找到路径与直径不重合

在这里插入图片描述在这里插入图片描述

通过树的直径性质,我们可以通过两次dfs预处理出来树的直径两个端点到所有点的距离,然后遍历所有点计算

代码
#include<bits/stdc++.h>

#define x first
#define y second
#define C(i) str[0][i]!=str[1][i]
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =2e5+10,INF=0x3f3f3f3f;
int n,k,c;
vector<int> e[N];
int d1[N],d2[N],d3[N];

void dfs(int d[],int u,int fa){
    for(auto v:e[u]){
        if(v!=fa){
            d[v]=d[u]+1;
            dfs(d,v,u);
        }
    }
}

void solve() {  
    cin >>n >>k >>c;
    for(int i=1;i<=n;i++)   e[i].clear();
    for(int i=0;i<n-1;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    d1[1]=0;
    ///以根节点1为源点进行DFS找到距离该点最远的点,该点即为树的直径的一个端点
    dfs(d1,1,1);
    int p1=max_element(d1+1,d1+n+1)-d1;
    d2[p1]=0;
    ///以树的直径的一个端点为起始点,处理得到其余点到树的直径其中一个端点的距离
    dfs(d2,p1,p1);
    int p2=max_element(d2+1,d2+n+1)-d2;///与树的直径的一个端点相距最远的点为,直径的另一个端点
    d3[p2]=0;
    dfs(d3,p2,p2);

    LL ans=0;
    for(int i=1;i<=n;i++){
        ///遍历每一个节点作为根节点的情况,其价值为到直径端点的最远距离*k-到节点1的边数*c
        ans=max(ans,(LL)max(d2[i],d3[i])*k-(LL)c*d1[i]);
    }

    cout <<ans <<"\n";
}   

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}
 

3.方法2使用换根DP,每次当前结点为根时,维护向下走的两个最长距离以及向上走的最长距离

将每个节点的最大距离分情况讨论,一种为向下走,一种为向上走,通过两次搜索,得到每个节点向下走和向上走的最长距离,然后通过遍历每个节点来分别计算最大价值

代码
#include<bits/stdc++.h>

#define x first
#define y second
#define C(i) str[0][i]!=str[1][i]
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =2e5+10,INF=0x3f3f3f3f;
int n,k,c;
vector<int> e[N];
int d1[N],d2[N],up[N],p[N],d3[N];

int dfs_d(int u,int fa){
    d1[u]=d2[u]=-INF;
    for(auto v:e[u]){
        if(v!=fa){
            d3[v]=d3[u]+1;
            int val=dfs_d(v,u);///先更新子节点
            ///更新最大距离和次大距离
            if(d1[u]<=val){
                d2[u]=d1[u];
                d1[u]=val;
                p[u]=v;
            }else if(d2[u]<val){
                d2[u]=val;
            }
        }
    }
    if(d1[u]==-INF){
        d1[u]=0;
        p[u]=0;
    }
    return d1[u]+1;
}

void dfs_u(int u,int fa){
    for(auto v:e[u]){
        if(v!=fa){
            ///向外走的路径不能再折回来,故当v向上走的父节点其向下走的最大距离所对应节点为v
            ///则需要更换为次大距离
            if(p[u]!=v) up[v]=max(up[u],d1[u])+1;
            else up[v]=max(up[u],d2[u])+1; 

            dfs_u(v,u);///更新完父节点后再更新子节点
        }
    }
}

void solve() {  
    cin >>n >>k >>c;
    for(int i=1;i<=n;i++)   e[i].clear();
    for(int i=0;i<n-1;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    d3[1]=0;
    ///获得每个节点向下走的两个最大距离
    dfs_d(1,1);
    up[1]=0;
    ///获得每个节点向上走的最大距离
    dfs_u(1,1);

    LL ans=0;
    for(int i=1;i<=n;i++){
        ans=max(ans,(LL)max(up[i],d1[i])*k-(LL)d3[i]*c);
    }
    cout <<ans <<"\n";
}   

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

4.方法3,采用树形DP的思想,考虑每个节点为根节点的子树内可得的最大距离

若要每个节点都考虑该节点为根的子树内所产生的最大距离,则可以分成两种情况,一种为该节点做根向下的最大距离,另一种为有两段该节点向下的路径合并在一起的最大距离,以其中一个较短的路径端点为根。

代码
#include<bits/stdc++.h>

#define x first
#define y second
#define C(i) str[0][i]!=str[1][i]
using namespace std;
typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N =2e5+10,INF=0x3f3f3f3f;
int n,k,c;
vector<int> e[N];
LL maxval;

LL dfs(int u,int fa,int d){
    int ma1=0,ma2=0,res=0;
    for(auto v:e[u]){
        if(v!=fa){
            ///先更新子节点,获得子节点为根的子树可得到的最大距离
            int val=dfs(v,u,d+1);
            res=max(res,val);
            ///更新最大值和次大值
            ma2=max(ma2,val);
            if(ma1<ma2) swap(ma1,ma2);
        }
    }
    ///计算当前结点为根时可得到的最大价值,d+ma2是以m2路径所在端点点为根
    maxval=max({maxval,(LL)k*ma1-(LL)c*d,(LL)k*(ma1+ma2)-(LL)c*(d+ma2)});
    return res+1;
}

void solve() {  
    cin >>n >>k >>c;
    for(int i=1;i<=n;i++)   e[i].clear();
    for(int i=0;i<n-1;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    maxval=0;
    dfs(1,1,0); 
    cout <<maxval<<"\n";
}   

int main() {
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值