感觉这类问题很少?算了,还是拿出来水一下吧qwq...


首先来看一道例题:POJ3585

一句话题意:树上任意源点多汇点最大流

你看这不就是个最大流的板子题吗?我先建个图,然后跑最大流,然后,,,然后?

然后

All the elements are nonnegative integers no more than 200000.

还多测... mdzz

好了来讲换根吧。

首先有一个暴力\(dp\),我们先考虑以\(rt=1\)的情况(\(1\)为根),令\(dp[x]\)表示\(rt\)为根时,直接往节点\(x\)灌水,然后向\(x\)的子树内流的最大流量

有如下转移

\[dp[x] = \sum\limits_{v \in son_x} \min\{dp[v], w(x,v)\}
\]

特别的,如果\(v\)在\(1\)为根的情况下是叶节点的话,\(dp[x] += w(x,v)\)(\(w(x,y)\)表示边\((x,y)\)的容量)

然后\(O(n)\)枚举\(rt\),再\(O(n)\)dp,就可以收获一个\(n^2\)的优秀做法。

发现换根过后树的变化实际上没有那么大,我们令\(f[x]\)表示\(rt=x\)时的\(dp[x]\)。

考虑把\(rt\)换到\(x\)的一个儿子\(v\)上,由于\(x\)为根的时候\(v\)也是\(x\)的一个儿子,所以\(v\)会对\(f[x]\)产生\(min(dp[v],w(x,v))\)的贡献

那么\(f[x]-min(dp[v],w(x,v))\)就得到了除了子树\(v\)以外的部分,它在换根后会成为\(v\)的一棵子树

所以\(f[v] = dp[v]+min\left(w_{x,v},f[x]-min(dp[v],w_{x,v})\right)\)(嘛...括号太多看不清楚...所以这里就直接\(w_{x,v}\)了qwq)

需要特判\(x\)只有\(v\)一个儿子的时候\(f[v] = dp[v]+w_{x,v}\)(就直接从\(v\)流向\(x\)了)

所以Code

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <set>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define fore(i,x) for(int i=head[x],v=e[i].to,w=e[i].w;i;i=e[i].nxt,v=e[i].to,w=e[i].w)
const int N=3e5+10;
struct edge
{
int to,nxt,w;
}e[N<<1];
int head[N],cnt;
inline void ade(int x,int y,int w)
{e[++cnt]=(edge){y,head[x],w},head[x]=cnt;}
inline void addedge(int x,int y,int w)
{ade(x,y,w),ade(y,x,w);}
int n,sum[N],f[N],deg[N]; // 这里的sum就是上面的dp辣
void dfs(int x,int prev)
{
fore(i,x) if(v!=prev)
{
dfs(v,x);
if(deg[v]==1) sum[x]+=w;
else sum[x]+=min(sum[v],w);
}
}
void getans(int x,int prev)
{
fore(i,x) if(v!=prev)
{
if(deg[x]==1) f[v]=sum[v]+w;
else f[v]=sum[v]+min(f[x]-min(sum[v],w),w);
getans(v,x);
}
}
void sol()
{
memset(deg,0,sizeof(deg));
memset(sum,0,sizeof(sum));
memset(f,0,sizeof(f));
memset(head,0,sizeof(head));
memset(e,0,sizeof(e));
cnt=0;
scanf("%d",&n);
for(int x,y,w,i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&w);
addedge(x,y,w);
++deg[x],++deg[y];
}
dfs(1,0);
f[1]=sum[1],getans(1,0);
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,f[i]);
printf("%d\n",ans);
}
int main()
{
int T; scanf("%d",&T); while(T--)sol();
return 0;
}

于是再来看一道题:LuoguP3478

同样换根,令\(f[x]\)表示\(x\)为根时的所有节点的深度和。

考虑将根换到\(x\)的一个儿子\(v\)上,那么对于子树\(v\)里面的节点,他们的深度会减少\(1\),其他节点深度会加上\(1\),即(\(size_v\)表示\(v\)以\(1\)为根时的子树大小)

\[f[v]=f[x]+n-size_v-size_v=f[x]+n-2\times size_v
\]

先以\(1\)为根求出\(size\),然后换根就好了

十年OI一场空,不开long long见祖宗

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define fore(i,x) for(int i=head[x],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
const int N=1e6+10;
int n;
struct edge
{
int to,nxt;
}e[N<<1];
int head[N],cnt=0;
inline void ade(int x,int y)
{e[++cnt]=(edge){y,head[x]},head[x]=cnt;}
inline void addedge(int x,int y)
{ade(x,y),ade(y,x);}
ll f[N],dep[N];
int siz[N];
void dfs(int x,int prev,int deep)
{
dep[x]=deep,siz[x]=1;
fore(i,x) if(v!=prev)
dfs(v,x,deep+1),siz[x]+=siz[v];
}
void getans(int x,int prev)
{
fore(i,x) if(v!=prev)
{
f[v]=f[x]+n-2ll*siz[v];
getans(v,x);
}
}
int main()
{
scanf("%d",&n);
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),addedge(x,y);
dfs(1,0,0);
for(int i=1;i<=n;i++) f[1]+=dep[i];
getans(1,0);
ll mx=0; int ans=0;
for(int i=1;i<=n;i++)
if(f[i]>mx) mx=f[i],ans=i;
printf("%d\n",ans);
return 0;
}

换根dp的更多相关文章

  1. [BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]

    题意 给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值. \(n\leq 5\times 10^5\) . 分析 记断掉一条边之后两棵树的直径为 \ ...

  2. 2018.10.15 NOIP训练 水流成河(换根dp)

    传送门 换根dp入门题. 貌似李煜东的书上讲过? 不记得了. 先推出以1为根时的答案. 然后考虑向儿子转移. 我们记f[p]f[p]f[p]表示原树中以ppp为根的子树的答案. g[p]g[p]g[p ...

  3. 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市

    P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...

  4. 小奇的仓库:换根dp

    一道很好的换根dp题.考场上现场yy十分愉快 给定树,求每个点的到其它所有点的距离异或上m之后的值,n=100000,m<=16 只能线性复杂度求解,m又小得奇怪.或者带一个log像kx一样打一 ...

  5. 国家集训队 Crash 的文明世界(第二类斯特林数+换根dp)

    题意 ​ 题目链接:https://www.luogu.org/problem/P4827 ​ 给定一棵 \(n\) 个节点的树和一个常数 \(k\) ,对于树上的每一个节点 \(i\) ,求出 \( ...

  6. Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)

    题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...

  7. bzoj 3566: [SHOI2014]概率充电器 数学期望+换根dp

    题意:给定一颗树,树上每个点通电概率为 $q[i]$%,每条边通电的概率为 $p[i]$%,求期望充入电的点的个数. 期望在任何时候都具有线性性,所以可以分别求每个点通电的概率(这种情况下期望=概率 ...

  8. codeforces1156D 0-1-Tree 换根dp

    题目传送门 题意: 给定一棵n个点的边权为0或1的树,一条合法的路径(x,y)(x≠y)满足,从x走到y,一旦经过边权为1的边,就不能再经过边权为0的边,求有多少边满足条件? 思路: 首先,这道题也可 ...

  9. [Bzoj3743][Coci2015] Kamp【换根Dp】

    Online Judge:Bzoj3743 Label:换根Dp,维护最长/次长链 题目描述 一颗树n个点,n-1条边,经过每条边都要花费一定的时间,任意两个点都是联通的. 有K个人(分布在K个不同的 ...

  10. 洛谷$P3647\ [APIO2014]$连珠线 换根$dp$

    正解:换根$dp$ 解题报告: 传送门! 谁能想到$9102$年了$gql$居然还没写过换根$dp$呢,,,$/kel$ 考虑固定了从哪个点开始之后,以这个点作为根,蓝线只可能是直上直下的,形如&qu ...

随机推荐

  1. 【快学springboot】4.接口参数校验

    前言 在开发接口的时候,参数校验是必不可少的.参数的类型,长度等规则,在开发初期都应该由产品经理或者技术负责人等来约定.如果不对入参做校验,很有可能会因为一些不合法的参数而导致系统出现异常. 上一篇文 ...

  2. PHP 获取一周的时间

    $date = array( 'Monday' => array(date('Y-m-d H:i:s',strtotime("Monday")),date('Y-m-d H: ...

  3. 本地虚拟机搭建ES集群

    一.环境说明 1.物理机信息(主要): 内存:8G 系统/主频:Win7(旗舰版)64位/3.70GHZ 2.虚拟机信息: VMware Workstation 14 Pro 下载地址: 链接:htt ...

  4. SpringCloud实战——(2)通过Feign调用其他模块

    大型项目下往往有很多模块,ZCGL项目结构如下: 需要引用的其他模块已经发布成服务并在Eureka Server注册中心注册,如下: 写程序时引用了其他模块,并且其他模块在项目中,但是IDEA任然无法 ...

  5. 如何在adapter 中调用activity的方法

    如何在adapter 中调用activity的方法 2015-08-07 17:06匿名 | 浏览 808 次  iWorkjavaAndroid public class HistoryData e ...

  6. 用 lastIndexOf()、substr()、split()方法截取一段字符串

    lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索. split() 方法用于把一个字符串分割成字符串数组,抽取到分割符前面部分. subst ...

  7. 「CF650E」Clockwork Bomb

    传送门 Luogu 解题思路 显然对于两棵树共有的边,我们不会动它. 考虑第二颗树中有和第一棵树不同的边怎么处理. 我们设 \(fa_1[u],fa_2[u]\) 分别代表 \(u\) 在两棵树中的父 ...

  8. 「PA2014」Kuglarz

    传送门 memset0好评 解题思路 其实这是一道图论题 不难发现,如果知道了 \(\sum1...i\) 和 \(\sum1...j\) 的奇偶性,那么就可以得知 \(\sum i+1...j\) ...

  9. 简单看看ReentrantLock

    前面我们分析了AQS的基本原理,然后也试着基于AQS实现了一个可重入的锁了,现在我们再来看看官方的ReentrantLock锁,这个锁是可重入的独占锁,也就是说同时只有一个线程可以获取该锁,而且这个线 ...

  10. mysql里的序列应用详解

    相关知识库: MySQL知识库 相关文章: ibatis中主键的返回 oracle 实现自增auto_increament 用oracle的trigger生成主键的时候和hibernate冲突的讨论 ...