首页 > 其他分享 >Living-Dream 系列笔记 第71期

Living-Dream 系列笔记 第71期

时间:2024-08-01 19:50:24浏览次数:7  
标签:Living cur nxt int siz 71 Dream 节点 dp

众所周知,换根 dp 是非常套路的。换根真好玩(

换根 dp:

当不同节点作为根时,dp 结果不一致,若枚举每个节点作为根,则时间复杂度过高,在此种情形下,可使用 换根 dp 处理相邻两节点间的贡献,从而达到快速换根的效果。

使用场景:

对于一棵树,寻找以某节点 \(u\) 为根时取得的 最大值 / 最小值 / 方案数

实现步骤:

  1. 任选一节点作为根,跑一遍树形 dp,得到 \(dp_i\) 表示以 \(i\) 根的子树的 最大值 / 最小值 / 方案数。
  2. 令 \(f_i\) 表示以 \(i\) 为 全局根 时的 最大值 / 最小值 / 方案数,初始 \(f_1=dp_1\)。
  3. 从根再次 dfs,自父节点向子节点转移 \(f_i\)。

P3478

令 \(dp_i\) 表示以 \(i\) 为根的子树的节点全局深度之和。

(令 \(dp_i\) 表示为全局 / 局部信息依题而定,哪个方便做选哪个)

初始:\(dp_{cur}=dep_{cur}\)。

转移:\(dp_{cur}=dp_{cur}+dp_i\)(\(cur\) 为 \(i\) 的父节点)。

令 \(f_i\) 表示以 \(i\) 为全局根的节点深度之和。

初始:\(f_1=dp_1\)。

答案:\(\max\{f_i\}\)。

转移:

image

如图,以 \(nxt\) 为根的子树往上升,其子树内所有点的深度会减 \(1\);而以 \(cur\) 为根的子树往下降,其子树内所有点的深度会加 \(1\)。

于是有转移:

\[f_{nxt}=f_{cur}-siz_{nxt}+(n-siz_{nxt}) \]

\[=f_{cur}+n-2 \times siz_{nxt} \]

(\(siz_{nxt}\) 表示以 \(nxt\) 为根的子树大小)。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e6+5;
int n;
vector<int> G[N<<1];
int dp[N],f[N],siz[N],dep[N];

void dfs1(int cur,int fa){
	siz[cur]=1;
	dp[cur]=dep[cur];
	for(int i:G[cur]){
		if(i==fa) continue;
		dfs1(i,cur);
		dep[i]=dep[cur]+1;
		siz[cur]+=siz[i];
		dp[cur]+=dp[i];
	}
}
void dfs2(int cur,int fa){
	for(int i:G[cur]){
		if(i==fa) continue;
		f[i]=f[cur]+n-2*siz[i];
		dfs2(i,cur);
	}
}

signed main(){
	cin>>n;
	for(int i=1,u,v;i<n;i++)
		cin>>u>>v,
		G[u].push_back(v),
		G[v].push_back(u);
	dep[1]=1;
	dfs1(1,0);
	f[1]=dp[1];
	dfs2(1,0);
	int ans=0,p=0;
	for(int i=1;i<=n;i++)
		if(ans<f[i])
			ans=f[i],p=i;
	cout<<p; 
	return 0;
}

P2986

这题实质即为上题加个边权。

令 \(dp_i\) 表示以 \(i\) 为根的子树的节点到它的带权路径和(局部)。

初始:\(dp_{cur}=0\)。

转移:\(dp_{cur}=dp_cur+dp_i+siz_i \times w\)(\(cur\) 为 \(i\) 的父节点,\(w\) 表示边 \(cur \to i\) 的边权)。

令 \(f_i\) 表示以 \(i\) 为全局根的带权路径和的最小值。

初始:\(f_1=dp_1\)。

答案:\(\min\{f_i\}\)。

转移:

image

如图,以 \(nxt\) 为根的子树往上升,子树内贡献不变,且子树内的所有节点都无需经过 \(cur \to nxt\) 这条边;以 \(cur\) 为根的子树往下降,子树内的所有节点都必须经过 \(cur \to nxt\) 这条边。

于是有转移:

\[f_{nxt}=(dp_{nxt}-siz{nxt} \times w)+((f_{cur}-dp_{nxt}) + (tot-siz_{nxt}) \times w) \]

\[=dp_{nxt}+(tot-2 \times siz_{nxt}) \times w \]

(\(w\) 表示 \(cur \to nxt\) 这条边的边权,\(siz_{nxt}\) 表示 \(nxt\) 子树内的牛的数量

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e6+5;
int n,tot,c[N];
struct E{ int v,w; };
vector<E> G[N<<1];
int dp[N],f[N],siz[N];

void dfs1(int cur,int fa){
	siz[cur]=c[cur];
	dp[cur]=0;
	for(auto i:G[cur]){
		if(i.v==fa) continue;
		dfs1(i.v,cur);
		siz[cur]+=siz[i.v];
		dp[cur]+=dp[i.v]+siz[i.v]*i.w;
	}
}
void dfs2(int cur,int fa){
	for(auto i:G[cur]){
		if(i.v==fa) continue;
		f[i.v]=f[cur]+(tot-2*siz[i.v])*i.w;
		dfs2(i.v,cur);
	}
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) 
		cin>>c[i],tot+=c[i];
	for(int i=1,u,v,w;i<n;i++)
		cin>>u>>v>>w,
		G[u].push_back({v,w}),
		G[v].push_back({u,w});
	dfs1(1,0);
	f[1]=dp[1];
	dfs2(1,0);
	int ans=1e18;
	for(int i=1;i<=n;i++)
		ans=min(ans,f[i]);
	cout<<ans; 
	return 0;
}

CF1187E

诈骗题。

我们先按照常规套路进行分析。

容易发现在第一次选点后的选点操作都是固定的。考虑换根 dp。

令 \(dp_i\) 表示以 \(i\) 为根的子树的全局最大权值(当然局部也可)。

初始:\(dp_{cur}=siz_{cur}\)(\(siz_i\) 表示以 \(i\) 为根的子树大小)。

转移:\(dp_{cur}=dp_{cur}+dp_i\)。

令 \(f_i\) 表示以 \(i\) 为全局根的最大权值。

初始:\(f_1=dp_1\)。

转移:

image

如图,以 \(nxt\) 为根的子树往上升,子树内贡献不变,且子树内所有节点均无需对以 \(cur\) 为根的子树产生贡献;以 \(cur\) 为根的子树往下降,子树内的所有节点都必须对以 \(nxt\) 为根的子树产生贡献。

于是有转移:

\[f_{nxt}=dp_{nxt}+(f_{cur}-dp_{nxt}-siz_{nxt})+(n-siz_{cur}) \]

\[=f_{cur}+n-2 \times siz_{cur} \]

然后我们发现这就是 P3478 的转移方程。

这是因为每次进行染色,贡献都是染色节点的子树大小。

而每次染色后下一个被染色的一定是它的子节点,

这就导致每个节点在它的每一个祖先染色时都贡献了 \(1\),

加起来就是它的深度,

因此所有点的贡献之和就是深度之和。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e6+5;
int n;
vector<int> G[N<<1];
int dp[N],f[N],siz[N];

void dfs1(int cur,int fa){
	siz[cur]=1;
	for(int i:G[cur]){
		if(i==fa) continue;
		dfs1(i,cur);
		siz[cur]+=siz[i];
		dp[cur]+=dp[i];
	}
	dp[cur]+=siz[cur];
}
void dfs2(int cur,int fa){
	for(int i:G[cur]){
		if(i==fa) continue;
		f[i]=f[cur]+n-2*siz[i];
		dfs2(i,cur);
	}
}

signed main(){
	cin>>n;
	for(int i=1,u,v;i<n;i++)
		cin>>u>>v,
		G[u].push_back(v),
		G[v].push_back(u);
	//dep[1]=1;
	dfs1(1,0);
	f[1]=dp[1];
	dfs2(1,0);
	int ans=0;
	for(int i=1;i<=n;i++)
		if(ans<f[i])
			ans=f[i];
	cout<<ans; 
	return 0;
}

CF1324F

看到 \(0,1\) 求贡献,首先考虑将 \(0\) 转化为 \(-1\)

于是这题通过转化后,求差变为了求和。

接着我们发现,选包含某个点的连通子图,则必须包含其子树。

又因为要求出每个点的最大值,因此这题就变成了 换根版 的 最大子树和。

\(dp_i\) 按照那题求出即可。

令 \(f_i\) 表示以 \(i\) 为全局根的最大值。

初始:\(f_1=dp_1\)。

答案:所有的 \(f_i\)。

转移:

子树是必选的,子树外的如果贡献 \(>0\) 则可选,否则不选。

另外,子树外的贡献由子树内的贡献决定,如果子树内贡献 \(>0\),则子树外的要去掉子树内的贡献,否则可以全选。

于是有转移:

\[f_{nxt}=dp_{nxt}+\max(\max(f_{cur}-\max(dp_{nxt},0),0)) \]

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+5;
int n,a[N];
int dp[N],f[N];
vector<int> G[N<<1];

void dfs1(int cur,int fa){
	dp[cur]=a[cur];
	for(int i:G[cur]){
		if(i==fa) continue;
		dfs1(i,cur);
		dp[cur]=max(dp[cur],dp[cur]+dp[i]);
	}
}
void dfs2(int cur,int fa){
	for(int i:G[cur]){
		if(i==fa) continue;
		f[i]=dp[i]+max(f[cur]-max(dp[i],0),0);
		dfs2(i,cur);
	}
}

int main(){
	cin>>n;
	for(int i=1,x;i<=n;i++)
		cin>>x,a[i]=(x?x:-1);
	for(int i=1,u,v;i<n;i++)
		cin>>u>>v,
		G[u].push_back(v),
		G[v].push_back(u);
	dfs1(1,0);
	f[1]=dp[1];
	dfs2(1,0);
	for(int i=1;i<=n;i++)
		cout<<f[i]<<' ';
	return 0;
}

标签:Living,cur,nxt,int,siz,71,Dream,节点,dp
From: https://www.cnblogs.com/XOF-0-0/p/18337360

相关文章

  • CF716B Complete the Word 题解
    CF716BCompletetheWord题解分析首先观察数据范围是\(50000\),可以考虑\(O(n)\)暴力。在字符串中枚举子串开始的位置\(i\),然后再枚举\(i\)到\(i+25\),开个桶统计每个大写字母出现的次数,如果大于\(1\)就直接break。统计完之后剩下的就都是问号了,可以随便填,所以这个子......
  • 题解:CF718A Efim and Strange Grade
    CF718AEfimandStrangeGrade题解算法贪心+模拟思路分析显然,要最优每一次进位就只能五入不能四舍。而且当我们五入时,要取位数最高的。比如说\(1.3535\),我们有两种进位方式,一种是进位成\(1.4\),一种是进位成\(1.354\),显然前者更优。那这道题给的次数有啥用呢?考虑一种“......
  • ctfshow-web入门-sql注入(web171-web175)
    目录1、web1712、web1723、web1734、web1745、web1751、web171单引号测一下,报错 --+闭合后回显正常 也可以用#,不过需要URL编码成功闭合之后,先判断下字段数:1'orderby3--+3的时候正常 4的时候报错,说明只有3列  测了一下,三个回显位都能正......
  • 洛谷-P1171-售货员的难题
    Abstract传送门也算是状压dp模板题?不过这个数据给的有点阴间了,空间不够用,需要搞一个奇妙的优化。idea所谓状压,就是用数字表示当前状态,比如说0110100这个数字,我们可以把01分别看作是是否到达过第i个点的标记。那么我们可以用dp[i][j]表示第i个状态下,快递员到达j......
  • Living-Dream 系列笔记 第70期
    登神长阶!P1272&P1273请查阅往期笔记,此处不再赘述。其中P1273我们学到了定义更好求解的状态(一般是转化为价值,如本题),再通过枚举求解最终答案。P8625容易发现你选出的\(S\)一定是一个子树。然后这题就变成最大子树和了。关于最大子树和那题,请查阅往期笔记,此处不再赘述。......
  • Living-Dream 系列笔记 第69期
    复习树形dp。树形dp定义状态一般套路:令\(dp_i\)表示以\(i\)为子树的xxx(要维护的信息),可以有多维,但一定会有这一维。P2016&P2014请查阅往期笔记,此处不再赘述。P2585以前是分讨每个节点有几个儿子,然后分别转移。其实不用分讨,直接将所有节点视作有两个儿子,初始时将它......
  • 多肽合成: SLIGRL-NH2 (Synonyms: Protease-Activated Receptor-2 Activating Peptide)
    SLIGRL-NH2(Protease-ActivatedReceptor-2ActivatingPeptide)是一种蛋白酶激活受体-2(PAR-2)激动剂。 中文名称:SER-LEU-ILE-GLY-ARG-LEU-NH2英文名称:SLIGRL-NH2CAS号:171436-38-7分子式:C29H56N10O7分子量:656.82序列:Ser-Leu-Ile-Gly-Arg-Leu-NH2单字母......
  • luogu P2371 [国家集训队] 墨墨的等式 题解
    luoguP2371[国家集训队]墨墨的等式题目传送门思路同余最短路同余最短路同余最短路与差分约束有异曲同工之妙,都将约束条件转化为边,每种状态转化为点。把本来与图论毫不相干的问题抽象到具体的图上,通过拓扑排序,最短路等基础算法获得最小状态,从而解决问题。在本题中,以\(0\)......
  • Living-Dream 系列笔记 第67期
    树上倍增:维护\(dp_{i,j}\)表示节点\(i\)向上移动\(2^j\)步所到达的节点编号、区间最值、区间和等信息。倍增求LCA:预处理:令\(dp_{i,j}\)表示\(i\)向上走\(2^j\)步所到达的节点。转移:\(dp_{i,j}=dp_{dp_{i,j-1},j-1}\)。初始:\(dp_{i,0}=fa_i\)。查询......
  • 2024世界人工智能大会:智象未来(HiDream.ai)入围多行业示范性应用案例
    在刚刚闭幕的世界人工智能大会(2024WAIC)上,智象未来(HiDream.ai)依托自身领先的行业技术,入围多行业示范性应用案例,充分展示了其在人工智能领域的卓越成就和创新能力。会上,智象未来(HiDream.ai)联合创始人兼CTO姚霆博士正式推出了备受期待的“智象大模型2.0”。新一代多模态大模型......