启发式合并加树形dp

news2024/9/19 20:31:44

题目链接

        

       令f【x】【0】表示不选根的x子树的最大贡献,f【x】【1】表示选根的x子树最大贡献,g【x】为max(f【x】【0】,f【x】【1】)。

        如果我们要连接x和u1,那么贡献是:

        w【x】+w【u1】+f【u1】【0】+(f【u2】【0】-g【u1】)+(f【u3】【0】-g【u2】)........+(f【uk】【0】-g【uk-1】)。

        

        我们先考虑只有一个v点的时候,它有两个孩子v1和v2.如果我们要选v那么我们只能从不跨根的v1和v2里选,也就是f【v】【0】。

        然后我们考虑有两个v的时候。除了上面的f【v1】【0】外,我们还要算g【v5】(v2也在被选的路径上,不能再选)。容易知道f【v2】【0】包括了两部分,一个是v1为根的子树,一个是v5为根的子树。我们要算v5的子树贡献就只需要让f【v2】【0】减去v1子树的贡献,也就是g【v1】,所以v5的贡献就是(f【v2】【0】-g【v1】)。

        我们将路径上的点不断扩充,也就得到了一开始的那个式子。我们发现那个式子括号里的下标并不对齐,我们等价变形一下:

        w【x】+w【u1】+(f【u1】【0】-g【u1】)+(f【u2】【0】-g【u2】)....+(f【uk】【0】-g【uk】)+g【uk】。因为差了一个g【uk】我们把它添加一个再减去仍然是相等的。

        现在我们观察这个式子,对于后面那些括号的和其实就是路径上的每个点的f【x】【0】-g【x】的和。

        然后我们现在再回到一开始的问题,我们要让根x去其子树去找一个颜色相同的点连线当做路线。w【x】是不变的,所以在挑选的时候我们可以不用管w【x】。我们令sum=f【x】【0】,表示sum是每个子树都不跨根的贡献。显然这样每个子树都是互相不影响的,他们的贡献是可以直接求和当做x的贡献的。

        我们现在考虑选一个子树来连接根。因为我们选了这个子树,所以我们要先把这个子树的贡献先去掉,然后加上新贡献。令该子树为v,减贡献的操作就是sum-g【v】。我们再回到上面的式子w【x】+w【u1】+(f【u1】【0】-g【u1】)+(f【u2】【0】-g【u2】)....+(f【uk】【0】-g【uk】)+g【uk】。g【uk】其实就是这个g【v】,就是x的直接儿子。也就是说其实我们只要把式子的最后一项去掉,即w【x】+w【u1】+(f【u1】【0】-g【u1】)+(f【u2】【0】-g【u2】)....+(f【uk】【0】-g【uk】)就是这个路径新添加的贡献,也就是说新贡献就是w【x】+w【u1】+(f【u1】【0】-g【u1】)+(f【u2】【0】-g【u2】)....+(f【uk】【0】-g【uk】)+sum。

        w【x】是固定的,sum是固定的,所以对于这颗子树,我们只需要让w【u1】+(f【u1】【0】-g【u1】)+(f【u2】【0】-g【u2】)....+(f【uk】【0】-g【uk】)这个式子最大,就是我们要从这颗树考虑的贡献了。

        因为我们之前把括号里的下标处理成一样的了,所以我们可以考虑在u1回溯的时候,在每层dfs结束后再把每层的f【u】【0】-g【u】给u1的贡献加上。最后我们只需要从子树里和根x颜色相同的节点u里去找该贡献最大的点即可。

        因为我们要将儿子节点的信息在回溯的时候更新,所以我们需要每次回溯到父节点的时候都去更新所有子节点的信息。

        暴力合并和暴力修改肯定是不行的....所以对于合并用启发式合并,对于更新添加贡献的操作就用类似线段树的lazy标记来标记整个连通块...

        代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
const int N=2e5+10;
const int inf=0x3f3f3f3f;
typedef pair<int,int> pii;
typedef unsigned long long ull;
//const ll P=2281701377;
const ll P=998244353;

int n,c[N],w[N];
vector<int> e[N];
ll f[N][2],g[N],bl[N];//0,1表示取不取根节点,g表示f的0,1里的最大值 
ll tag[N];//bl表示每个子树连通块map的祖先节点,tag表示连通块的标记,只标记在祖先节点,表示选了这个点,不选这条路径上的所有点子树会减少的最大贡献 
map<int,ll> mp[N];//存连通块某种颜色的w【某种颜色的某个节点】+那个节点到当前节点的(f【路上节点,0】-g【路上节点】)之和的最大值 
void upd(ll &x,ll y){
	x=max(x,y);
}
void dfs(int x,int fa){
	f[x][0]=f[x][1]=g[x]=0;
	for(auto to:e[x]){
		if(to==fa) continue;
		dfs(to,x);
		f[x][0]+=g[to];//不取根就可以取所有子树的情况之和 
	}//因为子树就算取子树根也不会取到当前节点,每个子树没有影响 
	for(auto v:e[x]){
		if(v==fa) continue;
		if(mp[bl[v]].count(c[x])){//根为端点 
			upd(f[x][1],(mp[bl[v]][c[x]]+tag[bl[v]])+f[x][0]+w[x]);//tag【bl【v】】就表示bl【v】这个连通块共同添加的贡献 
		}//因为现在遍历的点都是x的儿子节点,也就是说x回溯后新添加的贡献对他们都是一样,只要比当前的贡献就可以了,因为后面贡献都是一样的 
		if(mp[bl[v]].size()>mp[bl[x]].size()){
			swap(bl[v],bl[x]);//启发式合并,siz小连到siz大,祖先节点表示一个连通块 
		}//如果子树节点个数大,那么bl【x】=bl【v】,bl【v】=bl【x】,这时候mp【bl【x】】实际上是子树的mp
		//tag【bl【x】】也是子树的tag,直接交换祖先节点就可以交换所有信息了 
		for(auto [col,val]:mp[bl[v]]){//遍历子树 
			if(mp[bl[x]].count(col)){//看其他之前的子树有没有这个颜色(不包括当前子树) 
				upd(f[x][1],(val+tag[bl[v]])+(mp[bl[x]][col]+tag[bl[x]])+f[x][0]);
			}//val+tag才是它自己现在的贡献   mp+tag也才是mp真实的贡献 每层都让f【x】【0】-g【x】实际上多减了最后一次的g【x】
			//也就是说直接算val+tag【bl【v】】的时候减去了g【直接儿子】,mp【bl【x】】【col】+tag【bl【x】】也减去了那颗子树与x的直接儿子的g【直接儿子】 
		}

		for(auto [c,val]:mp[bl[v]]){
			if(mp[bl[x]].count(c)){//用当前子树信息与前面子树信息来更新,这里实际上就是把v的信息合并到x上了 
				upd(mp[bl[x]][c],val+tag[bl[v]]-tag[bl[x]]);//因为我们只需要保留贡献最大的点即可,所以比较保留max,且因为他们后面会添加的贡献都是相同的 
			}
			else{
				mp[bl[x]][c]=val+tag[bl[v]]-tag[bl[x]];//因为tag是对子树内所有点进行添加贡献的 
			}//但是如果是新添加进去的点,它在父亲子树同一加贡献的时候并不属于父亲子树,所以父亲子树添加的贡献并不能加在它身上 
		}	//但我们并不能修改父亲子树的tag,所以我们只能修改新添加的子树属于自身的贡献,我们令它减去父亲子树的tag, 
	} 		//那么在最后算它贡献的时候再加上父亲子树的tag,等价于它本身的贡献是没有被父亲子树的tag影响的
			//val是w【u】+它加入父亲子树前的tag才是它加入前的自身贡献 
	if(mp[bl[x]].count(c[x])){//用自己和所有子树信息来更新 
		upd(mp[bl[x]][c[x]],w[x]-tag[bl[x]]);
	}//同理 mp【bl【x】】【c【x】】的实际值是 mp【bl【x】】【c【x】】+tag【bl【x】】,因为我们tag要对区间操作不能每个点修改 
	else mp[bl[x]][c[x]]=w[x]-tag[bl[x]];//所以我们将后来区间共同添加的贡献都放到了tag里,我们得把mp加上这些区间共同加的tag贡献 
	g[x]=max(f[x][0],f[x][1]);//和线段树的lz标记一样 
	tag[bl[x]]+=f[x][0]-g[x];//每次都会把所有子节点和当前节点都合并到一个集合也就是(bl【x】),
	//将这个节点的祖先节点标记上f【x】【0】-g【x】等于将整个子树的节点都新添了这个贡献 ,类似线段树的lz标记 
}
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>c[i];
	}
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	for(int i=1;i<=n;i++){
		bl[i]=i,tag[i]=0,mp[i].clear();
		e[i].clear();
	}
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	dfs(1,-1);
	cout<<g[1]<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--){
        solve();
    }

}


        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1983346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASP.NET Core 基础 - 入门实例

一. 下载 1. 下载vs2022 Visual Studio 2022 IDE - 适用于软件开发人员的编程工具 (microsoft.com) 学生,个人开发者选择社区版就行,免费的. 安装程序一直下一步下一步就行,别忘了选择安装位置,如果都放在C盘的话,就太大了. 2. 选择工作负荷 准备工作完成 二. 创建新项目 三…

如何用密码保护你的 WordPress 管理员 (wp-admin) 目录

在维护 WordPress 网站时&#xff0c;确保 wp-admin 目录安全是非常重要的。为该目录添加密码保护可以有效提高网站安全性&#xff0c;防止未经授权的访问。这篇文章将介绍实现这一目标的两种方法。 1.为什么要为 wp-admin 目录添加密码保护 WordPress 管理员后台是网站的核心…

自动化集成应用钡铼DB系列防水分线盒

随着工业自动化的快速发展&#xff0c;如今的现场设备需要更高效、更稳定的信号采集和集成方案。钡铼技术的DB系列防水分线盒作为一种优秀的解决方案&#xff0c;成功地结合了先进的工业设计与耐用材料&#xff0c;为物流设备、食品加工设备、制药设备等多种工业应用提供了可靠…

《深入浅出WPF》学习笔记六.手动实现Mvvm

《深入浅出WPF》学习笔记六.手动实现Mvvm demo的层级结构,Mvvm常用项目结构 依赖属性基类实现 具体底层原理后续学习中再探讨,可以粗浅理解为,有一个全局对象使用list或者dic监听所有依赖属性,当一个依赖属性变化引发通知时,就会遍历查询对应的字典&#xff0c;通知View层进行…

目标检测之选择性搜索:Selective Search

文章目录 一.选择性搜索的具体算法二.保持多样性的策略三.给区域打分四.选择性搜索性能评估五.代码实现 论文地址&#xff1a; https://www.koen.me/research/selectivesearch/ 代码地址&#xff1a; https://github.com/AlpacaDB/selectivesearch 参考&#xff1a; https:/…

SpringBootWeb AOP

事务&AOP 1. 事务管理 1.1 事务回顾 在数据库阶段我们已学习过事务了&#xff0c;我们讲到&#xff1a; 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位。事务会把所有的操作作为一个整体&#xff0c;一起向数据库提交或者是撤销操作请求。所以这组操作要…

kickstart自动安装脚本,pxe网络安装

目录 1 kickstart图形化生成脚本工具 1.1 安装apache 1.2 创建挂载镜像软链接 1.3 图形生成自动化脚本选项 1.4 修改生成的自动化脚本 1.5 将脚本放至网站根目录 2 安装系统 2.1 关闭DHCP自动分配 2.2 下载配置DHCP服务 2.3 重启DHCP服务 2.4 使用pxe方法安装系统&#xff08;网…

YOLOv5与YOLOv8 训练准备工作(不包含环境搭建)

前言&#xff1a;我发现除了安装环境需要耗费大量时间以外&#xff0c;对于训练前的准备工作也要琢磨一段时间&#xff0c;所以本篇主要讲一下训练前需要准备的工作&#xff08;主要是XML格式换为txt&#xff0c;以及划分数据集验证集&#xff0c;和训练参数的设置&#xff09;…

8–9月,​Sui Move智能合约工作坊将在台北+线上举行

你对区块链和去中心化应用感兴趣吗&#xff1f;想深入学习Sui Move编程语言吗&#xff1f; 从8月10日到9月28日&#xff0c;Sui Mover社区将在每周六下午13:00–17:00举办精彩的工作坊&#xff0c;为期两个月&#xff0c;带你从零基础入门到高级进阶&#xff0c;全面掌握Sui M…

Django配置模板引擎

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) 本节主要介…

Linux之进程间通信(上)

目录 进程间通信的目的 进程通信的分类 进程通信之匿名管道 创建匿名管道 匿名管道的特点 匿名管道四种通信类型 在现实生活中&#xff0c;人们要进行合作&#xff0c;就必须进行交流&#xff0c;那么在进程之间&#xff0c;会存在交流的情景吗&#xff1f;答案是肯定的…

音频转换器在线哪个好?提升音频质量的转换工具

你是否曾梦想过将手机里的铃声变成自己的原创作品&#xff1f;或者&#xff0c;有没有想过将一段演讲录音转化为易于分享的MP3格式&#xff1f; 如果答案是肯定的&#xff0c;那么这款音频转换器mp3就是你寻找的答案。现在&#xff0c;让我们一起探索它的魅力吧&#xff01; 一…

基于MATLAB机器学习、深度学习实践技术

近年来&#xff0c;MATLAB在机器学习和深度学习领域的发展取得了显著成就。其强大的计算能力和灵活的编程环境使其成为科研人员和工程师的首选工具。在无人驾驶汽车、医学影像智能诊疗、ImageNet竞赛等热门领域&#xff0c;MATLAB提供了丰富的算法库和工具箱&#xff0c;极大地…

浏览器用户文件夹详解 - Preferences(十)

1.Preferences简介 1.1 什么是Preferences文件&#xff1f; Preferences文件是Chromium浏览器中用于存储用户个性化设置和配置的一个重要文件。每当用户在浏览器中更改设置或安装扩展程序时&#xff0c;这些信息都会被记录在Preferences文件中。通过这些记录&#xff0c;浏览…

海思ISP记录七:低照度图像调整

Hi3519av100imx585 记录下低照度图像调整的流程和思路 一、环境与配置 环境&#xff1a;暗房不开灯&#xff0c;只有零散漏光和电脑光亮gain与帧率&#xff1a;根据手册我设置的是Again&#xff1a;31282&#xff1b;Dgain&#xff1a;8192&#xff1b;ISP Dgain&#xff1a;…

B1.1 关于应用程序员模型-概述

快速链接: . 👉👉👉 ARMv8/ARMv9架构入门到精通-[目录] 👈👈👈 付费专栏-付费课程 【购买须知】个人博客笔记导读目录(全部) B1.1 关于应用程序员模型–概述 本章节包含了应用程序开发所需的程序员模型信息。 本章节中的信息不同于支持和服务于操作系统下应用程…

1.MySQL面试题之innodb如何解决幻读

1. 写在前面 在数据库系统中&#xff0c;幻读&#xff08;Phantom Read&#xff09;是指在一个事务中&#xff0c;两次读取同一范围的数据集时&#xff0c;由于其他事务的插入操作&#xff0c;导致第二次读取结果集发生变化的问题。InnoDB 作为 MySQL 的一个存储引擎&#xff…

PyTorch深度学习实战(2)——PyTorch快速入门

PyTorch的简洁设计使得它易于入门&#xff0c;在深入介绍PyTorch之前&#xff0c;本文先介绍一些PyTorch的基础知识&#xff0c;以便读者能够对PyTorch有一个大致的了解&#xff0c;并能够用PyTorch搭建一个简单的神经网络。 1 Tensor Tensor是PyTorch中最重要的数据结构&#…

docker、k8s部署 mysql group replication 和 ProxySQL 读写分离

MySQL Group Replication&#xff08;简称MGR&#xff09;是MySQL官方推出的一个高可用与高扩展的解决方案。MySQL组复制它提供了高可用、高扩展、高可靠的MySQL集群服务&#xff0c;这里部署的 mysql 版本 5.7.33&#xff0c;架构是一读一写。特别要注意一个关键点: 必须保证各…

sqli-labs-php7-master第11-16关

猜注入点 先来猜数字型 单引号字符型&#xff1a; 发现注入点找到了 猜测数据库有多少个字段&#xff1a; 1’ order by 4 # 密码随便输的。 这里没有使用--注释&#xff0c;因为没作用&#xff0c;可能是过滤掉了 继续猜。刚才没猜对 1 order by 2 # 没报错&#xff0c;猜…