首页 > 其他分享 >CSP2025 - 树形 DP

CSP2025 - 树形 DP

时间:2025-01-17 14:44:23浏览次数:1  
标签:int siz CSP2025 times 树形 MAXN text fno DP

CSP2025 - 树形 DP

T1 「MXOI Round 1」城市

这个 “树上两点距离之和” 很典,让我们想到换根 DP。

首先求出 \(\text{siz}_u\) 和 \(d_u\),分别表示子树 \(u\) 的大小和子树所有点到 \(u\) 的距离之和。

接下来求出整棵树的所有点到 \(\boldsymbol u\) 的距离之和。考虑用 \(d_u\) 推出 \(d_v\),我们发现从 \(d_u\) 到 \(d_v\) 少了 \(\text{siz}_v\) 个 \(w(u,v)\) 的贡献,多了 \(n-\text{siz}_v\) 个 \(w(u,v)\) 的贡献,所以第二遍 DFS 的转移方程就是

\[d_v=d_u+(n-2\times\text{siz}_v)\times w(u,v)~. \]

然后考虑第 \(n+1\) 个点加入的情况。对于新建的 \((k,n+1)\) 这条边,会在 \(\sum d_i\) 的基础上多出 \(2\times\big(d_k+n\times w(k,n+1)\big)\) 的贡献(乘 \(2\) 是因为双向边),于是查询就 \(O(1)\) 了。

总体复杂度 \(O(n)\)。

int siz[MAXN],d[MAXN],ans;
void dfs(int u,int fno){
	siz[u]=1;
	for(int i=head[u];i;i=e[i].to){
		if(e[i].v==fno) continue;
		dfs(e[i].v,u);
		siz[u]+=siz[e[i].v];
		add(d[u],(d[e[i].v]+1ll*siz[e[i].v]*e[i].w%MOD)%MOD);
	}
}
void dfs2(int u,int fno){
	for(int i=head[u];i;i=e[i].to)
		if(e[i].v^fno){
			d[e[i].v]=(d[u]+(n-2ll*siz[e[i].v]%MOD+MOD)%MOD*e[i].w%MOD)%MOD;
			dfs2(e[i].v,u);
		}
}

int main(){
	n=read(),q=read();
	for(int i=1,u,v,w;i<n;++i){
		u=read(),v=read(),w=read();
		addedge(u,v,w);
		addedge(v,u,w);
	}
	dfs(1,0);
	dfs2(1,0);
	for(int i=1;i<=n;++i) add(ans,d[i]);
	while(q--){
		int k=read(),w=read();
		write((ans+2ll*d[k]+2ll*n*w)%MOD);
	}
	return fw,0;
}

T2 [CF1285D] Dr. Evil Underscores

其实这题就是个贪心。首先异或 \(\to\) 字典树。把所有数扔到字典树上,然后按二进制位从高到低贪:如果当前节点有两个儿子,取两个儿子的最小值再加上当前位置的贡献;如果有一个儿子,就填成和这个儿子相同的位,不产生贡献。

就没了。

int n,tot=1;
int trie[MAXN][2];
void insert(int x){
	int p=1;
	for(int i=30;~i;--i){
		int c=(x>>i)&1;
		if(!trie[p][c]) trie[p][c]=++tot;
		p=trie[p][c];
	}
}
int ask(int bit,int p){
	if(bit<0) return 0;
	if(trie[p][0]&&trie[p][1]) return min(ask(bit-1,trie[p][0]),ask(bit-1,trie[p][1]))+(1<<bit);
	if(trie[p][0]) return ask(bit-1,trie[p][0]);
	if(trie[p][1]) return ask(bit-1,trie[p][1]);
	return 0;
}

int main(){
	n=read();
	for(int i=1;i<=n;++i) insert(read());
	printf("%d\n",ask(30,1));
	return 0;
}

T3 P1270 “访问”美术馆

感觉这题的读入难度大于 DP 难度

首先建边的时候就把边权建成 \(2\) 倍,这样就计算了往返的时间。然后树上背包,设 \(f_{i,j}\) 表示节点 \(i\) 剩余时间为 \(j\) 的最大答案。则有转移方程

\[f_{u,i}=\max_{v\in\text{son}_u,w(u,v)\le j\le i}\{f_{u,i-j}+f_{v,j-w(u,v)}\}~. \]

在叶子节点时,初始化边界条件 \(f_{u,i}=\min(a_u,f_{u,i-5}+1)\)。

注意如果转移的时候 \(i\) 从 \(n\) 到 \(w\) 枚举复杂度会假,所以我们需要处理 \(\text{siz}_u\) 表示偷完 \(u\) 点所有的东西再回到 \(u\) 点所需要的时间,然后优化转移上界就不会 T 了。\(j\) 也可以优化。

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

constexpr int MAXN=6005;
int n,tot=2,son[MAXN],siz[MAXN],f[MAXN][MAXN];
vector<pair<int,int>>g[MAXN];
void init(int u,int fno){
	int w,v;
	scanf("%d%d",&w,&v);
	g[fno].emplace_back(u,w<<1);
	if(v) return son[u]=v,void();
	init(++tot,u);
	init(++tot,u);
}
void dfs(int u){
	if(son[u]){
		siz[u]=son[u]*5;
		for(int i=5;i<=n;++i) f[u][i]=min(son[u],f[u][i-5]+1);
		return;
	}
	for(auto vv:g[u]){
		int v=vv.first,w=vv.second;
		dfs(v);
		siz[u]+=siz[v]+w;
		for(int i=min(n,siz[u]);i>=w;--i)
			for(int j=w;j<=min(i,siz[v]+w);++j)
				f[u][i]=max(f[u][i],f[u][i-j]+f[v][j-w]);
	}
}

int main(){
	scanf("%d",&n);
	init(2,1);
	dfs(1);
	printf("%d\n",f[1][n-1]);
	return 0;
}

T4 [HAOI2015] 树上染色

将 “黑点两两距离” 和 “白点两两距离” 转化为针对计算贡献,也就是对于每一条边所经过的次数,乘上边权再加起来就是总的答案。

而每一条边被经过的次数等于边的两侧同色点的个数的乘积。于是一条边被经过的次数就是:

\[\mathit{tot}=k(m-k)+(\text{siz}_v-k)(n-m-\text{siz}_v+k) \]

其中 \(m\) 是题目要求选的黑点数,\(\text{siz}_v\) 表示 \(v\) 的子树大小,\(k\) 表示当前子树上已选择的黑点数。

DP 方程又是很熟悉的树上背包。设 \(f_{i,j}\) 表示节点 \(i\) 选 \(j\) 个黑点的答案,有

\[f_{u,j}=\max_{v\in\text{son}_u}\{f_{u,j-k}+f_{v,k}+\mathit{tot}\times w(u,v)\} \]

树上背包需要通过 \(\textbf{siz}\) 优化转移边界。把 \(k=0\) 的情况提到循环外面作为边界的预处理。

ll siz[MAXN],f[MAXN][MAXN];
void dfs(int u,int fno){
	siz[u]=1;
	f[u][0]=f[u][1]=0;
	for(int i=head[u],v,w;i;i=e[i].to){
		if((w=e[i].w,v=e[i].v)==fno) continue;
		dfs(v,u);
		siz[u]+=siz[v];
		for(int j=min(1ll*m,siz[u]);~j;--j){
			if(~f[u][j]) f[u][j]+=f[v][0]+siz[v]*(n-m-siz[v])*w;
			for(int k=min(1ll*j,siz[v]);k;--k)
				if(~f[u][j-k]){
					ll tot=1ll*k*(m-k)+(siz[v]-k)*(n-m-siz[v]+k);
					f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]+tot*w);
				}
		}
	}
}

int main(){
	scanf("%d%d",&n,&m);
	if(n-m<m) m=n-m;
	for(int i=1,u,v,w;i<n;++i){
		scanf("%d%d%d",&u,&v,&w);
		addedge(u,v,w),addedge(v,u,w);
	}
	memset(f,-1,sizeof(f));
	dfs(1,0);
	printf("%lld\n",f[1][m]);
	return 0;
}

T9 P5007 DDOSvoid 的疑惑

手画一下发现一定是同父亲的两个子树之间会产生贡献,这个贡献的产生方式为:在这些子树中任选一些,把这些子树的权值加起来就是贡献。

于是,我们需要记录两个值:\(f_u\) 和 \(g_u\),分别表示节点 \(u\) 的贡献和 “毒瘤集” 的数量。先考虑 \(g_u\) 的转移。把上面所说的 “任选一些” 转化为:每新增一个点 \(v\),都可以和已有的所有点相匹配,所以有

\[g_u\gets g_u+g_u\times g_v~. \]

然后考虑 \(f_u\) 的转移,依然是每新增一个点都可以和之前的所有点匹配,有

\[f_u\gets f_u+f_u\times g_v+f_v\times g_u+f_v~. \]

至于这里为什么要分别算两遍 \(f_u\times g_v\) 和 \(f_v\times g_u\),个人认为是因为多个元素构成的集合所产生的贡献也是多个元素的,所以匹配两遍才不会算漏。但为什么不会算重,题解区好像没有合理的解释,只能说 DP 还是太深奥了。

最后还需要给 \(f,g\) 加上当前点的贡献。

ll f[MAXN],g[MAXN];
void dfs(int u,int fno){
	for(int i=head[u],v;i;i=e[i].to){
		if((v=e[i].v)==fno) continue;
		dfs(v,u);
		add(f[u],f[u]*g[v]%MOD+f[v]*g[u]%MOD+f[v]);
		add(g[u],g[u]*g[v]%MOD+g[v]);
	}
	add(f[u],T?u:1);
	++g[u];
}

int main(){
	n=read(),T=read();
	for(int i=1,u,v;i<n;++i){
		u=read(),v=read();
		addedge(u,v),addedge(v,u);
	}
	dfs(1,0);
	printf("%lld\n",f[1]);
	return 0;
}

标签:int,siz,CSP2025,times,树形,MAXN,text,fno,DP
From: https://www.cnblogs.com/laoshan-plus/p/18593615

相关文章

  • ThreadPool解析
    Thread_Pool项目解析简介ThreadPool是一个轻量级的C++线程池实现,旨在简化多线程编程。项目分析我们首先上github的项目地址:https://github.com/progschj/ThreadPool,然后克隆项目到本地。点开项目的ThrealPool.h文件,查看源码:#ifndefTHREAD_POOL_H#defineTHREAD_POOL......
  • 树型DP
    ##树型DP**树型DP**,即在树上做动态规划。树是无环图,顺序可以是从叶子到根节点,也可以从根到叶子节点。一般树型DP的特征很明显,即状态可以表示为树中的节点,每个节点的状态可以由其子节点状态转移而来(从叶子到根的顺序),或是由其父亲节点转移而来(从根到叶节点的顺序),也可是两者结合。......
  • LeetCode | 图文详细描述动态规划DP算法及经典题型
    本文将用简单直白的方式,从零开始带你掌握动态规划的精髓。你会发现:动态规划其实没那么难——它就是递归的“记性”版。状态转移方程不再玄学——从题目思路到实现,手把手教你推导。经典题型剖析——从“爬楼梯”到“背包问题”,全都有图、有代码、有思路。看完这篇文章,动态规划......
  • wordpress 从服务器收到预料之外的响应。此文件可能已被成功上传。请检查媒体库或刷新
    两种报错方式:1.此响应不是合法的JSON响应。2.从服务器收到预料之外的响应。此文件可能已被成功上传。请检查媒体库或刷新本页。情况:媒体服务器上传小文件没问题,大一点的文件报这个错误。原因:这是因为nginx限制了请求体大小方案:需要在nginx的虚拟机配置文件中添加:client_max_b......
  • 基于C语言实现UDP服务器
    UDP(UserDatagramProtocol,用户数据报协议)是一种无连接的传输层协议,适用于对实时性有较高要求的应用场景,如视频流传输、语音通信、在线游戏等。与TCP不同,UDP不保证数据的可靠性和顺序性,但其传输速度较快。本文将介绍如何使用C语言编写一个简单的UDP服务器程序,以及如何接收和处理......
  • GDPR——DPIA
    GDPR发布以后,DPIA(DataProtectionImpactAssessment)是数据控制者DataController必须履行的一项安全职责(详见GDPR第35条)。一、触发的时机:DPIA应当前置评估,在如下这些场景实施前:使用新技术:Ifyou’reusingnewtechnologies处理地理位置和行为信息(地理位置很容易理解,行......
  • 【Windows安全】日志分析:RDP暴力破解
    #应急响应免责声明:请勿使用本文中提到的技术进行非法测试或行为。使用本文中提供的信息或工具所造成的任何后果和损失由使用者自行承担,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。暴力破解是一种通过穷举所有可能的密码或密钥组合来突破身份验证或加密保......
  • wordpress的火车头商品发布接口
    <?phprequire'../wp-load.php';ini_set('memory_limit','1024M');set_time_limit(180);$top_cat='';#图片链接域名替换$image_host='';$start_time=microtime(true);$counter=0;//临时缓存$products=$sk......
  • 为WordPress网站设置第三方社交软件登录
    1.下载SuperSocializer外挂,为WordPress网站设置第三方社交软件登录由于wordpress配置的数据库是本地专用的,所以用户如果使用我们搭建的网站可能需要重新登陆,这无疑会是我们网站登录方面的痛点,所以使用第三方社交软件账号登录会很方便。2.使用域名登录网站昨天搭建网站的时候,使......
  • 插头DP记录
    AAA黑题批发。这个东西好像设问还挺广泛的,做到哪写到哪吧。得先了解一下轮廓线dp定义。概念设问广泛但是总体来说是连通性相关状压dp的一类设计方法。骨牌覆盖问题比如说,最简单的,问你\(n*m\)的棋盘格里能放多少\(1*2\)的骨牌。考虑把一个节点分为上下左右四个插头,从上......