《算法竞赛·快冲300题》每日一题:“直径点对”

news/2024/7/24 12:25:21 标签: 动态规划

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。

文章目录

  • 题目描述
  • 题解
  • C++代码
  • Java代码
  • Python代码

直径点对” ,链接: http://oj.ecustacm.cn/problem.php?id=1736

题目描述

【题目描述】 给你一个n个节点的树,编号为1到n。求存在多少对节点<u,v>,使得u到v的距离等于这棵树的直径。
   树的直径:树上最远的两个点的距离
   树上两点的距离:两点之间边的数量
   <1,2>和<2,1>属于两对节点
【输入格式】 第一行为正整数n(n≤300000)。接下来n-1行,每行两个数字u和v,表示点u和点v之间存在边
【输出格式】 输出一个数字表示答案。
【输入样例】

4
1 2
1 3
1 4

【输出样例】

6

题解

   求树的直径有两种方法[ 《算法竞赛》,清华大学出版社,罗勇军、郭卫斌著,231页,“4.7.2 树的直径”。]:
   (1)做两次DFS,第一次求一个任意点的最远点s,第二次求s的最远点t,s和t之间的距离就是树的直径。
   (2)树形DP。算法不难,但是解释有点长,见《算法竞赛》233页的说明。请仔细理解如何用树形DP求树的直径。
   以上两种方法,算法复杂度都为O(n),即只对每个节点处理O(1)次。
   本题用这两种方法都能求解。下面用树形DP求树的直径,求直径的同时统计距离为直径的节点对数量。
【重点】 树形DP。

C++代码

   定义状态dp[],dp[u]表示从u出发的最长路径的长度,这条路径的终点是u的一个叶子节点。
   定义num[],num[u]表示从u出发的最长路径的数量。
   定义maxlen,表示直径的长度,k是经过直径的节点对数量。
   详细解释见代码的注释。如果仍然不能理解,可以把代码中与num[]和k有关的第8、20、22、23、26、28、29行删除,剩下的代码就是《算法竞赛》233页的树形DP求直径的模板。然后再加上num[]和k的代码并理解。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 300010;
vector<int>e[N];
int dp[N];                   //dp[u]:从u出发的最长路径
int num[N];                  //num[u]:从u出发的最长路径数量
ll maxlen = 0, k = 0;        //直径的长度maxlen,经过直径的节点对数量k
void dfs(int u, int fa){
    dp[u] = 0;
    num[u] = 1;
    for(auto v : e[u]){
        if(v == fa)  continue;
        dfs(v, u);                   //继续深入,回溯时带回算好的dp[v]
        int now = dp[v] + 1;         //从u出发,经过子节点v的最长路径
        if(now + dp[u] > maxlen){    //此时dp[u]是不经过v,而经过其他子节点的最长路径
                                     //now+dp[u]是经过u的最长路径
            maxlen = now + dp[u];    //更新maxlen为经过u的最长路径
                                     //此时u、v可能在树的直径上。比较所有的maxlen,最大的就是树的直径
            k = num[u] * num[v];  //计算k,这个k可能重新赋值
        }
        else if(now + dp[u] == maxlen)  //把此时的len看成树的直径,如果20行的k更新,这里也会重算
            k += num[u] * num[v];
        if(now > dp[u]){            //u经过v的路径更长
            dp[u] = now;            //更新dp[u]为经过v的路径
            num[u] = num[v];        //v更可能在直径上,把经过u的最长路径数量更新为经过v的数量
        }
        else if(now == dp[u])       //相等,这也是最长路径
            num[u] += num[v];
    }
}
int main(){
    int n;   scanf("%d", &n);
    for(int i = 1; i < n; i++){
        int u, v;    scanf("%d%d", &u, &v);
        e[u].push_back(v);  //加边
        e[v].push_back(u);
    }
    dfs(1, 0);
    cout << k * 2 << endl;        //按题意u-v和v-u不同,所以乘以2
    return 0;
}

Java代码

import java.util.*;
import java.io.*;
 
public class Main {
    static FastReader scanner = new FastReader();
    static int N = 300010;
    static ArrayList<Integer>[] e = new ArrayList[N];
    static int[] dp = new int[N]; // dp[u]:从u出发的最长路径
    static int[] num = new int[N]; // num[u]:从u出发的最长路径数量
    static long maxlen = 0, k = 0; // 直径的长度maxlen,经过直径的节点对数量k 
    public static void main(String[] args) throws IOException {         
        int n = scanner.nextInt();
        for (int i = 1; i <= n; i++)   e[i] = new ArrayList<>();        
        for (int i = 1; i < n; i++) {
            int u = scanner.nextInt();
            int v = scanner.nextInt();
            e[u].add(v); // 加边
            e[v].add(u);
        }
        dfs(1, 0);
        System.out.println(k * 2); // 按题意u-v和v-u不同,所以乘以2
    } 
    public static void dfs(int u, int fa) {
        dp[u] = 0;
        num[u] = 1;
        for (int v : e[u]) {
            if (v == fa)  continue;
            dfs(v, u); // 继续深入,回溯时带回算好的dp[v]
            int now = dp[v] + 1; // 从u出发,经过子节点v的最长路径
            if (now + dp[u] > maxlen) { // 此时dp[u]是不经过v,而经过其他子节点的最长路径
                // now+dp[u]是经过u的最长路径
                maxlen = now + dp[u]; // 更新maxlen为经过u的最长路径
                // 此时u、v可能在树的直径上。比较所有的maxlen,最大的就是树的直径
                k = num[u] * num[v]; // 计算k,这个k可能重新赋值
            } else if (now + dp[u] == maxlen) 
// 把此时的len看成树的直径,如果34行的k更新,这里也会重算
                k += num[u] * num[v];
            if (now > dp[u]) { // u经过v的路径更长
                dp[u] = now; // 更新dp[u]为经过v的路径
                // v更可能在直径上,把经过u的最长路径数量更新为经过v的数量
                num[u] = num[v];
            } else if (now == dp[u]) // 相等,这也是最长路径
                num[u] += num[v];
        }
    } 
    static class FastReader {
        BufferedReader br;
        StringTokenizer st; 
        public FastReader() { br = new BufferedReader(new InputStreamReader(System.in));  }
         String next() {
            while (st == null || !st.hasMoreElements()) {
                try {st = new StringTokenizer(br.readLine());} 
                catch (IOException e) { e.printStackTrace();}
            }
            return st.nextToken();
        } 
        int nextInt() {  return Integer.parseInt(next()); } 
        String nextLine() {
            String str = "";
            try { str = br.readLine();} 
            catch (IOException e) { e.printStackTrace(); }
            return str;
        }
    }
}

Python代码

#pypy
from collections import defaultdict
import sys
sys.setrecursionlimit(300000)
 
e = defaultdict(list)
dp = [0] * 300010
num = [0] * 300010
maxlen = 0
k = 0
 
def dfs(u, fa):
    global maxlen, k
    dp[u] = 0
    num[u] = 1
    for v in e[u]:
        if v == fa:   continue
        dfs(v, u)
        now = dp[v] + 1
        if now + dp[u] > maxlen:
            maxlen = now + dp[u]
            k = num[u] * num[v]
        elif now + dp[u] == maxlen:  k += num[u] * num[v]
        if now > dp[u]:
            dp[u] = now
            num[u] = num[v]
        elif now == dp[u]:  num[u] += num[v]
 
n = int(input())
for i in range(1, n):
    u, v = map(int, input().split())
    e[u].append(v)
    e[v].append(u)
 
dfs(1, 0)
print(k * 2)

http://www.niftyadmin.cn/n/5007800.html

相关文章

概率最大的路径 -- dijkstra算法应用

1514. 概率最大的路径 标准的Dijkstra 算法计算的是最短路径&#xff0c;计算的是最⼩值&#xff0c;这题让你计算最⼤概率是⼀个最⼤值&#xff0c;怎么可能⽤ Dijkstra 算法呢&#xff1f; 重点说说最⼤值和最⼩值这个问题&#xff0c;其实 Dijkstra 和很多最优化算法⼀样&…

【C++ Core Guidelines解析】深入理解现代C++的特性和原理

文章目录 &#x1f468;‍⚖️《C Core Guidelines解析》的主要观点&#x1f468;‍&#x1f3eb;《C Core Guidelines解析》的主要内容&#x1f468;‍&#x1f4bb;作者介绍 &#x1f338;&#x1f338;&#x1f338;&#x1f337;&#x1f337;&#x1f337;&#x1f490;&a…

直饮水表和智能水表有什么区别?

直饮水表和智能水表是两种不同类型的水表&#xff0c;它们的主要区别在于功能和应用场景。下面小编整理了这两款水表的一些知识点&#xff0c;一起来看下吧! 直饮水表是一种特殊的水表&#xff0c;主要用于家庭和公共场所的直饮水系统中。它可以实时监测水的流量&#xff0c;帮…

每日一题 2594. 修车的最少时间

难度&#xff1a;中等 思路源于题目标签 “二分”&#xff1a; 二分的上界应该是所有车都给修车能力值最小的人修&#xff0c;下界我设为0每次搜索时判断当前时间下&#xff0c;每位机械工总共能修 n 辆车&#xff0c;n > cars 则右边界左移&#xff0c;否则左边界右移 c…

网络技术二十一:ACL包过滤

ACL包过滤 ACL 定义 访问控制列表 用于数据流的匹配和筛选 常见功能 访问控制&#xff1a;ACLPacket-filter 路由控制&#xff1a;ACLRoute-policy 流量控制&#xff1a;ACLQOS 引入 ACL (Access Control List&#xff0c;访问控制列表)是用来实现数据包识别功能的 ACL可…

【python零基础入门学习】python基础篇之系统模块调用shell命令执行(四)

本站以分享各种运维经验和运维所需要的技能为主 《python》&#xff1a;python零基础入门学习 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8》暂未更新 《docker学习》暂未更新 《ceph学习》ceph日常问题解…

MT36291 2.5A,高效型1.2MHz电流模式升压转换器芯片

MT36291 2.5A&#xff0c;高效型1.2MHz电流模式升压转换器芯片 特征 ●集成了80ms功率的MOSFET ●2.2V到16V的输入电压 ●1.2MHz固定开关频率 ●可调过电流保护&#xff1a; 0.5A ~2.5A ●内部2.5开关限流&#xff08;OC引脚浮动&#xff09; ●可调输出电压 ●内部补偿 ●过电…

mysql 5.7 解压版安装教程(之前是安装包,从其他电脑复制过来也适用)

Microsoft Windows [版本 10.0.19045.3030] (c) Microsoft Corporation。保留所有权利。C:\workspace_software\mysql5.7\bin>net start mysql 服务名无效。请键入 NET HELPMSG 2185 以获得更多的帮助。C:\workspace_software\mysql5.7\bin>mysqld -install Service succ…