首页 > 编程语言 >[算法学习笔记] 换根dp

[算法学习笔记] 换根dp

时间:2023-08-25 11:24:18浏览次数:38  
标签:include int 节点 算法 now 换根 dp d1

换根 dp 一般不会指定根节点,并且根节点的变化会对一些值进行改变。因此我们需要转移根。

换根 dp一般需要预处理一下一个节点的值,然后对于任意节点开始树上dp转移。

所以我们常用两次 dfs,第一次 dfs预处理,第二次 dfs为树上 dp。

一般比较套路。

接下来会给出一个典型例题。

典例1:Luogu P3478

题目链接:Luogu P3478

Solution

我们发现本题没有给定 root,而且 root 之间的转移会影响每个节点到根的简单路径上的边的数量。

那么这种变化之间有什么关联呢?

我们发现对于一条边 \(u-v\) ,其中 \(v\) 是儿子。如果从 \(u\) 到 \(v\),那么
\(v\) 及 \(v\) 的儿子深度都会 \(-1\),反之 \(v\) 上面的节点深度都会 \(+1\)。

这就是转移式!借鉴先前求树的重心经验,对于 \(v\) 上面的部分,用 \(n-num_v-1\) 即可。

我们需要预处理每个节点的深度,以及每个节点下面有多少个儿子。然后转移即可。

形式化地,设 \(num_k\) 表示以 \(k\) 为父节点,其下面的儿子个数。\(dep_k\) 表示 \(k\) 的深度。则有:

\(f_k=f_{now}-num_v+(n-num_v)=f_{now}-2\times num_v+n\)(\(now-v\) 表示一条边)

换根 dp 一般转移直接转移,因为显然当 root 确定时答案是显然确定的!最后求以哪个节点作为 root 最大即可。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
const int N = 1000100;
vector <int> Edge[N];
int n;
int dep[N],sz[N];
int f[N];
void dfs1(int now,int fa)
{
    int u = now;
    sz[u] = 1;
    dep[u] = dep[fa] + 1;
    for(int i=0;i<Edge[now].size();i++)
    {
        int v = Edge[now][i];
        if(v == fa) continue;
        dfs1(v,now);
        sz[now] += sz[v];
    }
}
void dfs2(int now,int fa)
{
    for(int i=0;i<Edge[now].size();i++)
    {
        int v = Edge[now][i];
        if(v == fa) continue;
        f[v] = f[now]-2*sz[v]+n;
        dfs2(v,now);
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        Edge[u].push_back(v);
        Edge[v].push_back(u);
    }
    dfs1(1,-1);
    dfs2(1,-1);
    int maxn = -1,maxn_num;
    for(int i=1;i<=n;i++)
    {
        if(f[i] > maxn)
        {
            maxn = f[i];
            maxn_num = i;
        }
    }
    cout<<maxn_num<<endl;
    return 0;
}

典例2:求树的中心

原题链接https://www.luogu.com.cn/problem/U262945

显然我们可以直接求直径

这里主要介绍换根 dp 的思路,个人认为换根 dp 思考非常自然。

设 \(f_i\) 表示以 \(i\) 为树的中心的答案。我们想到对于 \(i\),她要找距离其他节点最远的距离,是不是可以向下找和向上找?向下找非常容易,dfs 预处理即可。向上找好像无法直接搜...

我们可以转化,对于一条边 \(u-v\) ,其中 \(u\) 是父亲。是不是可以用 \(u\) 向下所能走到的最远距离来更新?

需要格外注意的是,由于我们都是简单路径,不能走回头路。所以如果 \(u\) 向下最大能走到 \(v\) 。那我们显然不能先走到 \(u\),再走回 \(v\)。所以用次大值更新。

形式化地,状态转移如下:

\(up_{v}=max(up_u,d1_u)+w(d1_u \ne v)\)

\(up_{v}=max(up_u,d2_u)+w(d1_u = v)\)

实现
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 100010;
const int INF = 0x3f3f3f3f;
typedef pair<int,int> PAIR;
int n;
vector <PAIR> Edge[N];
int d1[N],d2[N],s1[N],s2[N];
int up[N];
void dfs1(int now,int fa)
{
    for(int i=0;i<Edge[now].size();i++)
    {
        int v = Edge[now][i].first,w = Edge[now][i].second;
        if(v == fa) continue;
        dfs1(v,now);
        if(d1[v] + w >= d1[now])
        {
            d2[now] = d1[now];
            s2[now] = s1[now];
            d1[now] = d1[v] + w;
            s1[now] = v;
        }
        else if(d1[v] + w > d2[now])
        {
            d2[now] = d1[v] + w;
            s2[now] = v;
        }
    }
}
void dfs2(int now,int fa)
{
    for(int i=0;i<Edge[now].size();i++)
    {
        int v = Edge[now][i].first,w = Edge[now][i].second;
        if(v == fa) continue;
        int u = now;
        if(s1[now] == v)
        {
            up[v] = w + max(d2[u],up[u]);
        }
        else up[v] = w + max(d1[u],up[u]);
        dfs2(v,now);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        Edge[a].push_back(PAIR(b,c));
        Edge[b].push_back(PAIR(a,c));
    }
    dfs1(1,-1);
    dfs2(1,-1);
    int res = INF;
    for(int i=1;i<=n;i++) res = min(res,max(up[i],d1[i]));
    cout<<res<<endl;
    return 0;
}

标签:include,int,节点,算法,now,换根,dp,d1
From: https://www.cnblogs.com/SXqwq/p/17656420.html

相关文章

  • xtrabackup支持的压缩算法的变化
    最近在debain11中尝试使用xtrabackupversion8.0.32-26备份MySQL的时候,发现debain11中很难找到qpress的安装包。顺便看了一下xtrabackup支持的压缩算法。查看xtrabackupversion8.0.32-26的帮助信息:--compress[=name]Compressindividualbackupfilesusingthespecifie......
  • 2023下半年杭州/武汉/深圳NPDP产品经理国际认证开班啦
    产品经理国际资格认证NPDP是新产品开发方面的认证,集理论、方法与实践为一体的全方位的知识体系,为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。  【认证机构】 产品开发与管理协会(PDMA)成立于1979年,是全球范围内产品开发与管理专业人士最杰出的倡导者,协助个人、企业......
  • Lnton羚通算法算力云平台【OpenCV-Python】教程:如何理解SVM
    线性可分下图有两种类型的数据,红色和蓝色。在kNN中,对于一个测试数据,我们用来测量它与所有训练样本的距离,并取距离最小的一个。测量所有的距离需要大量的时间,存储所有的训练样本需要大量的内存。但是考虑到图像中给出的数据,我们需要那么多吗?考虑另一个想法。我们找到一条直线,f(x......
  • R语言之 dplyr 包
    文章和代码已经归档至【Github仓库:<https://github.com/timerring/dive-into-AI>】或者公众号【AIShareLab】回复R语言也可获取。这个包以一种统一的规范更高效地处理数据框。dplyr包里处理数据框的所有函数的第一个参数都是数据框名。下面以MASS包里的birthwt数据集为例,介......
  • Lnton羚通算法算力云平台基于RK3399核心板的nanoPC-T4进行线刷桌面版系统教程
    nanoPC-T4刷桌面准备好相关工具软件1.瑞芯微驱动助手,DriverAssitant_v4.52.系统固件,rk3399-usb-friendlydesktop-bionic-4.4-arm64-20220919百度网盘链接提取码:8888硬件10nanoPC-T42.键鼠3.显示器及连接线4.type-c连接线开始刷机在Windows上安装USB驱动助手;2.nanoPC上电,USB......
  • 【校招VIP】前端校招考点之页面转换算法
    考点介绍:在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。一、考点题目1......
  • 算法
    STL中算法是functiontemplate。算法看不见容器,对其一无所知,所以它所需要的一切信息都必须从itertor取得,而iterators(由容器提供)必须能够回答算法的所有提问,才能搭配该算法的所有操作。迭代器的分类:structinput_iterator_tag{};structoutput_iterator_tag{};structf......
  • 关于安装Ambari 2.7.5 + HDP3.1.5
    参考文档安装Ambari2.7.5+HDP3.1.5(附安装包)_ambari安装包下载_不饿同学的博客-CSDN博客关于第11点,在浏览器输入http://hostname显示不了,要使用该hostname-ip才可以显示关于14,没找到maven-3.8.2而是使用了maven-3.8.8关于安装Ambari&HDP的配置libtirpc-devel本地源,该libtirpc......
  • python-优化算法应用于20种工程优化设计问题
     20种(全网最全)限制性工程设计问题(全网唯一python版):获取链接:https://mbd.pub/o/bread/ZJ2WlZls%1.Threebartrussdesign三杆桁架设计%2.Weldedbeamstructureproblem焊接梁结构问题%3.tension/Compressionspringdesignproblem张力/压缩弹簧设计问题%4.SpeedRe......
  • 【HDP】jupyter配置pyspark
    source/usr/hdp/3.3.1.0-002/spark2/bin/load-spark-env.shnohupjupyternotebook--no-browser--port18888--ip0.0.0.0--allow-root--NotebookApp.token=root>jupyter.log2>&1& 关闭INFO级别日志$SPARK_HOME/conf/log4j.propertieslog4j.rootCate......