当前位置: 首页 > article >正文

Acwing 5469. 有效点对【正难则反+巧妙选择根节点】

原题链接:https://www.acwing.com/problem/content/5472/

题目描述:

给定一个 n 个节点的无向树,节点编号 1∼n。

树上有两个不同的特殊点 x,y,对于树中的每一个点对 (u,v)(u≠v),如果从 u 到 v 的最短路径需要经过点 x 和点 y(路径的两个端点也算经过),且相对顺序上经过点 x,经过点 y,那么就称 (u,v) 是一个无效点对,否则就称 (u,v) 是一个有效点对。

请你计算树中有效点对的数量。

注意:

  1. (u,v) 和 (v,u) 是两个不同的点对。
  2. 有效点对必须满足 u≠v。

输入输出描述:

输入格式

第一行包含三个整数 n,x,y。

接下来 n−1 行,每行包含两个整数 a,b,表示点 a 和点 b 之间存在一条无向边。

输出格式

一个整数,表示有效点对的数量。

数据范围

前 3 个测试点满足 1≤n≤10。
所有测试点满足 1≤n≤3×10^5,1≤x,y≤n,x≠y,1≤a,b≤n,a≠b。

输入样例1:
3 1 3
1 2
2 3
输出样例1:
5
输入样例2:
3 1 3
1 2
1 3
输出样例2:
4

解题思路:

这个题目的关键就在于巧妙选择根结点方便计算,还有一个非常常用的方法就是正难则反,如果直接计算有效点对,显然存在多种情况,直接计算比较困难,这个时候就应该想到正难则反了,无效点对指的是先经过x点,再经过y点的路径,这个比较好计算,总的点对数为n*(n-1),只需要用总的点对数减去无效点对数就是有效点对数,下面画个图描述一下 ,如下图

我们考虑以x,y路径之间的点为根节点,同时让x在上面,那么就只会出现图1和图2这种情况了,就方便计算了。

对于图1,假设以x为根节点的子树中结点的个数是sizex,以y为根结点的子树中结点个数为sizey,那么这种形式的树中无效点对个数为sizex*sizey,那么有效点对个数就是n*(n-1)-sizex*sizey。

对于图2,可以认为是图1的一种特殊情况,需要特判一下,假设以y为根节点的子树大小为sizey,那么无效点对的起点是y子树中的某个点,无效点对的终点是y子树之外的任意一个点,那么无效点对的数目就是sizey*(n-sizey),那么有效点对的数目就是n*(n-1)-sizey*(n-sizey)。

对于图3,无效点对起点只能是y以及y以下的任意一个点,终点只能是x以及x以上的任意一个点,这个不是很好计算,我们可以以x,y路径上的某一个点为根节点,那么就可以避免图3这种情况,只需要考虑图1和图2这俩种情况即可,为了方便可以以y的父节点为根节点,那么就只需要考虑图1和图2这俩种情况了。

时间复杂度:O(n),n表示点数,因为边数为n-1。

空间复杂度:O(n)。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;

const int N=3e5+10,M=N*2;

int n,x,y;
int h[N],e[M],ne[M],idx;
int fa[N];
int sizex,sizey;

void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs1(int u,int father)
{
    fa[u]=father;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==father)continue;
        dfs1(j,u);
    }
}

int dfs2(int u,int father)
{
    int res=1;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==father)continue;
        int d=dfs2(j,u);
        res+=d;
        if(j==x)sizex=d;
        if(j==y)sizey=d;
    }
    return res;
}
int main()
{
    cin>>n>>x>>y;
    memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    
    dfs1(x,-1);  //第一个dfs是为了让x在上面,y在下面减少需要讨论的情况
    dfs2(fa[y],-1);  //第二个dfs以x,y路径上的某一个点为根节点方便计算,为了方便,不妨以y的父节点为根节点
    
    if(fa[y]==x){  //图2情况
        cout<<(LL)n*(n-1)-(LL)sizey*(n-sizey);
    }else {  //图1情况
        cout<<(LL)n*(n-1)-(LL)sizex*sizey;
    }
    return 0;
}

http://www.kler.cn/news/234224.html

相关文章:

  • Netty应用(四) 之 Reactor模型 零拷贝
  • 【算法】排序详解(快速排序,堆排序,归并排序,插入排序,希尔排序,选择排序,冒泡排序)
  • OpenCV-32 膨胀操作
  • 2024PMP考试新考纲-近年PMP真题练一练和很详细解析(3)
  • 【java】简单的Java语言控制台程序
  • golang select两个channel性能稳定,三个channel时性能会发生抖动,为什么?
  • (c语言版)数组去重和排序 题目描述: 给定一个乱序的数组,删除所有的重复元素,使得每个元素只出现一次,并且按照出现的次数从高到低
  • 设计模式-行为型模式(下)
  • 七、热身仪式(Warm-Up Rituals)
  • 《杨绛传:生活不易,保持优雅》读书摘录
  • Github 2024-02-10 开源项目日报Top10
  • Flink面试准备
  • 代码随想录算法训练营第三十三天丨[重看] 最大子数组、1143. 最长公共子序列/15.4 最长公共子序列
  • Android 识别车牌信息
  • Rust 格式化输出
  • TestNG基础教程
  • fast.ai 机器学习笔记(二)
  • JVM-类加载器 双亲委派机制
  • 项目02《游戏-13-开发》Unity3D
  • 在 Windows上恢复删除照片的 4 种有效方法
  • 初步探索Pyglet库:打造轻量级多媒体与游戏开发利器
  • 多维时序 | Matlab实现RF-Adaboost随机森林结合Adaboost多变量时间序列预测
  • hexo 博客搭建以及踩雷总结
  • 面向对象编程:理解其核心概念与应用
  • linux上部署ftp服务
  • MongoDB聚合:$replaceWith
  • visual studio和cmake如何编译dlib库
  • 深入了解 MySQL 数值型函数
  • Linux文本三剑客(2)
  • 【前后端的那些事】2万字详解WebRTC + 入门demo代码解析