6.12每日一题题解

巡逻

涉及知识点:

  • 树的直径/DFS/BFS/DP

solution:

树的直径的一道很经典的题目

因为这个题巧妙的把两种求树的直径的做法都体现的淋漓尽致

  • 我先来介绍一下树的直径吧:
  • 在一棵树里,每一条边都有权值(树里面两个点之间的边权),那么树上最远的距离就为树的直径,当然我们大多数做的都是边权为1的树,这个时候也就是树上距离最远的两个点
  • 求解树的直径的两种方法
  • 1.树形DP
  • ans 来记录树的直径的长度,那么ans是怎么记录的呢?dist[i]表示从节点出发可以到达的最远的距离
  • 对于一个节点x,dist[x]是x节点可以到达的最远的距离(也就是最长链的距离),y是x的一个子节点,那么dist[x]是否小于 dist[y] + ver[x][y]呢
  • 那么答案是有可能的,那么这个时候dist[x]链就变成了次长链的距离,ans = 应该为当前节点的最长链长度+当前节点的次长链的长度。首先x的最长链的长度为dist[x],而经过从y的这个节点延申的链的长度+ver[x][y]大于dist[x],那么dist[x]就次长链,而当时的次长链(假设为dist[z] + ver[x][z]),就变成了次次长链;之前的 ans = dist[x] + dist[z] + ver[z][x],那么此时的ans = dist[y] + ver[x][y] + dist[x]
  • 所以ans = max(ans,dist[x] + dist[y] + ver[x][y] ),并且我们还得让dist[x]为最长链的长度
  • dist[x] = max(dist[x],dist[y] + ver[x][y])
  • 看一下代码吧~
/*
	这里我用的vector来存储的边和距离
	原型为:	vector<pair<int,int>>v[N]
	st数组的原型为bool st[N]
	dist的原型为	dist[N]
*/

void dp(int u)
{
    dist[u] = 0;
    for(auto &x : v[u])
    {
        int j = x.first,w = x.second;
        if(st[j])continue;
        st[j] = true;
        dp(j);
        ans = max(ans,dist[j] + w + dist[u]);
        dist[u] = max(dist[u],dist[j] + w);
    }
}
//或者是这个代码,原理一样,就是把st数组换成了fa来判断
void dp(int u,int fa)
{
    dist[u] = 0;
    for(auto &x : v[u])
    {
        int j = x.first,w = x.second;
        if(j == fa)continue;
        dp(j,u);
        
        l2 = max(l2,dist[j] + w + dist[u]);
        
        dist[u] = max(dist[u],dist[j] + w);
    }
}

  • 2.DFS或BFS,两者在求得过程中原理是一样的
  • 两次搜索,第一次随便从某个点搜索,然后获取一个到达该点最远距离的点x,然后再从x点搜索,可以到达的最远距离(不适合边权有负的情况
  • 那么这个距离就是树的直径
  • 至于证明过程我就不证明了,点击此处,可看证明过程
  • 看一下代码吧
// dfs是从上往下传递的
void dfs(int u,int fa,int dis)
{
    dist[u] = dist[fa] + dis;
    
    pre[u] = fa;
    
    for(auto &x : v[u])
    {
        int j = x.first,w = x.second;
        if(j != fa){
            dfs(j,u,w);
        }
    }
}
  • 总结,两者各有优劣点,DP可以处理边权为负数的情况,而搜索不可以,搜索可以求出树的直径(这里我指的是路径),但是DP不可以。(我个人认为很麻烦,也当成了不可以,因为我们不需要做无力功)

  • 题目传送门

  • 思路:

  • 首先我们把树的直径给求出来,并且包括这条路,接着我们把这条路的边置为-1

  • 首先如果k为1的话,那么我们最终答案就是 (2*(n - 1) -l1+1)

  • 因为有n个点,n-1条边,那么我们至少把每条边走两次,所以答案为2*(n -1),如果此时我们把 树的直径给建立一条边,也就是从树的这头连向树的那头,此时我们就会会把这些边走一次,所以此时我们少走了l1,而我们得走这条边所以得+1。所以最终答案为(2*(n - 1) -l1+1)

  • 其次当k等于2的时候我们本可以通过同样的方法再次求出一个树的直径

  • 那么此时他可能与我们上一次求的树的直径的里面有重复的部分

  • 所以我们把第一次树的直径包含的边的权重设置为-1,这样即使我们所求的树的直径包含第一次我们所求的树的直径的一部分,那么这一部分我们也会加上,而不是再次减去

std:

#include <iostream>
#include <queue>
#include <cstring>
#include <vector>

using namespace std;

const int N = 1e5 + 20;

vector<pair<int,int>>v[N];
int n,k;

int dist[N],pre[N];

int l1 = 0,l2 = 0;

void dfs(int u,int fa,int dis)
{
    dist[u] = dist[fa] + dis;
    
    pre[u] = fa;
    
    for(auto &x : v[u])
    {
        int j = x.first,w = x.second;
        if(j != fa){
            dfs(j,u,w);
        }
    }
}

bool st[N];

void dp(int u)
{
    dist[u] = 0;
    for(auto &x : v[u])
    {
        int j = x.first,w = x.second;
        if(st[j])continue;
        st[j] = true;
        dp(j);
        
        l2 = max(l2,dist[j] + w + dist[u]);
        
        dist[u] = max(dist[u],dist[j] + w);
    }
}


int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> k;
    
    for(int i = 0;i < n - 1;i ++)
    {
        int a,b;
        cin >> a >> b;
        v[a].push_back({b,1});
        v[b].push_back({a,1});
    }
    
    int u = 1;
    
    dfs(1,0,0);
    
    for(int i = 2;i <= n;i ++){
        if(dist[u] < dist[i]){
            u = i;
        }
    }
    
    int a = u;
    memset(pre,0,sizeof pre);
    dfs(u,0,0);
    
    for(int i = 1;i <= n;i ++)
    {
        if(dist[u] < dist[i]){
            u = i;
        }
    }
    
    l1 = dist[u];

    if(k == 2){
        
        while(u != a)
        {
            int t = pre[u];
            for(auto &x : v[u])
            {
                if(x.first == t){
                    x.second = -1;
                    break;
                }
            }
            
            for(auto &x : v[t])
            {
                if(x.first == u){
                    x.second = -1;
                    break;
                }
            }
            
            u = pre[u];
        }
        memset(dist,0,sizeof dist);
        st[1] = true;
        dp(1);
    }
    // cout << l2 << endl;
    cout << 2*(n - 1) - l1 - l2 + k << endl;
    
    return 0;
}

CSDN博客链接

原文地址:https://www.cnblogs.com/QFNU-ACM/p/13100879.html