「CF1000G Two-Paths」
题目大意
给出一棵树,多次查询两个点之间的 \(\mathcal{Two-Path}\) 的权值的最大值,以下为 \(\mathcal{Two-Path}\) 路径权值的定义:
(其中 \(a_i\) 表示 \(i\) 的点权,\(w_e\) 表示边 \(e\) 的边权,\(k_e\) 表示边 \(e\) 被经过的次数,且要求 \(0\leq k_e\leq 2\),即每条边最多被走过两次).
分析
假如给出的树长这个样子:
考虑有一次 \(3\to 6\) 的查询.
显然对于 \(3\to 6\) 这条简单路径上的边是只能走一次的(如果走了至少 \(2\) 次,那么肯定要走至少 \(3\) 次,这样是不合法的),所以考虑贡献的时候只需要去考虑不在这条简单路径上的部分即可,也就是如下部分(蓝色和绿色部分):
其中绿色部分是路径上的点的部分子节点的贡献,蓝色部分仅在 \(\operatorname{LCA}(3,6)\)(每次查询的两个节点的最近公共祖先)处存在.先考虑绿色部分的贡献.
显然可以先处理出 \(f_i\) 表示 \(i\) 节点的所有子节点对 \(i\) 的贡献的最大值,显然有如下转移方程:
(\(VAL_u\) 表示 \(u\) 到 \(u\) 的父亲这条边的边权,\(val_u\) 表示 \(u\) 节点的权值)
但是对于两个点查询的时候并不能把路径上所以的 \(f\) 加起来,如下图:
我们需要的是蓝色部分以及绿色部分的贡献,但是对于 \(4\) 号节点存的贡献是红色部分给它的,但实际应该是只能有绿色部分,也就是说要减去蓝色部分的贡献,再理解理解后就可以得到以下式子:
(\(p\) 表示 \(i,fa\) 的部分对当前这次查询的贡献,\(fa\) 是 \(i\) 的父节点,\(fa,i\) 都是查询两个点再树上的简单路径上的点(\(i\) 为 \(u\) 或 \(v\),如果 \(i\not= u\) 且 \(i\not= v\),那么对于这条路径上的 \(i\) 的子节点也需要减去自身对 \(i\) 的贡献))
其中红色部分其实就是 \(i\) 对 \(fa\) 的贡献,将这部分拎出来就变成了所以非 \(\operatorname{LCA}\) 的节点都需要减去自己为根节点的子树对父节点的贡献.因为这个东西是固定的,所以可以直接树上差分快速计算.这样就可以通过计算一次 \(\operatorname{LCA}\) 的复杂度计算出所有路径节点的子树对路径的贡献.
下面只需要计算出 \(\operatorname{LCA}\) 的父节点对它的贡献即可,可以发现这个东西可以换根 \(\operatorname{dp}\) 求出:
(\(g_i\) 表示 \(i\) 的父节点对 \(i\) 的贡献,具体推导比较显然,不展开)
对于查询两点的简单路径的贡献也可以直接树上差分,所以单次查询的复杂度 \(=\) 计算两点 \(\operatorname{LCA}\) 的复杂度.
因为不会 \(\mathcal{Tarjan\ LCA}\) 所以用了树剖,但还是比倍增做法快了不少.
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;last<=i;--i)
namespace IO
//快读模板
using namespace IO;
using namespace std;
const int MAXN=3e5+5;
const int MAXM=MAXN<<1;
int n,m;
long long val[MAXN];
struct Edge
{
int to,next;
long long val;
}edge[MAXM];
int edge_head[MAXN];
int edge_cnt=0;
#define FOR(now) for(int edge_i=edge_head[now];edge_i;edge_i=edge[edge_i].next)
#define TO edge[edge_i].to
#define VAL edge[edge_i].val
inline void AddEdge(int from,int to,long long val)
{
edge[++edge_cnt].to=to;
edge[edge_cnt].val=val;
edge[edge_cnt].next=edge_head[from];
edge_head[from]=edge_cnt;
}
long long f[MAXN];//f 同分析中的 f
long long sumf[MAXN];//f 函数的树上前缀和
long long d[MAXN];//每个点对父节点的贡献
long long sumdec[MAXN];//需要减去部分的树上前缀和
long long tof[MAXN];//每个点到父节点的边的边权
long long sump[MAXN],sume[MAXN];//点权与边权的树上前缀和
long long g[MAXN];//g 同分析中的 g
//以下变量为树剖所需变量
int point_deep[MAXN];
int point_father[MAXN];
int point_son[MAXN];
int point_size[MAXN];
int chain_cnt=0;
int point_id[MAXN];
int point_val[MAXN];
int chain_top[MAXN];
//树剖部分不会做说明,如有不理解建议先做模板题
void DFS_1(int now=1)//第一次 DFS
{
sump[now]=sump[point_father[now]]+val[now];//树上前缀点权
int max_size=-1;
point_size[now]=1;
FOR(now)
{
if(point_father[now]!=TO)
{
point_father[TO]=now;
point_deep[TO]=point_deep[now]+1;
tof[TO]=VAL;
sume[TO]=sume[now]+VAL;//树上前缀边权
DFS_1(TO);
f[now]+=d[TO];//计算 f
point_size[now]+=point_size[TO];
if(point_size[TO]>max_size)
{
max_size=point_size[TO];
point_son[now]=TO;
}
}
}
d[now]=max(f[now]-2*tof[now]+val[now],0ll);//计算这个点对父节点的贡献
}
void DFS_2(int now=1,int top=1,long long on=0/*从父节点转移来的 gi*/)
{
g[now]=on;
sumf[now]=sumf[point_father[now]]+f[now];
sumdec[now]=sumdec[point_father[now]]+d[now];
point_id[now]=++chain_cnt;
point_val[chain_cnt]=val[now];
chain_top[now]=top;
if(!point_son[now])
{
return;
}
DFS_2(point_son[now],top,max(on+f[now]+val[now]-d[point_son[now]]-2*tof[point_son[now]],0ll));
FOR(now)
{
if(TO!=point_father[now]&&TO!=point_son[now])
{
DFS_2(TO,TO,max(on+f[now]+val[now]-d[TO]-2*VAL,0ll));
}
}
}
int LCA(int u,int v)
{
while(chain_top[u]!=chain_top[v])
{
if(point_deep[chain_top[u]]<point_deep[chain_top[v]])
{
swap(u,v);
}
u=point_father[chain_top[u]];
}
if(point_deep[v]<point_deep[u])
{
swap(u,v);
}
return u;
}
long long Query(int u,int v)
{
int lca=LCA(u,v);
return sumf[u]+sumf[v]-2*sumf[lca]+f[lca]-sumdec[u]-sumdec[v]+2*sumdec[lca]+g[lca]-sume[u]-sume[v]+2*sume[lca]+sump[u]+sump[v]-sump[lca]-sump[point_father[lca]];//又臭又长的树上差分
}
int main()
{
Read(n,m);
REP(i,1,n)
{
Read(val[i]);
}
int u,v,w;
REP(i,1,n-1)
{
Read(u,v,w);
AddEdge(u,v,w);
AddEdge(v,u,w);
}
DFS_1();
DFS_2();
REP(i,1,m)
{
Read(u,v);
Writeln(Query(u,v));
}
return 0;
}
原文地址:https://www.cnblogs.com/Sxy_Limit/p/13828798.html
- 微信文件微起底
- Go语言TCP Socket编程--1
- Go语言TCP Socket编程--2
- 服务器 数据库设计技巧--1
- CVE-2015-0235:Linux glibc高危漏洞的检测及修复方法
- zabbix监控在lnmp环境下编译安装小记
- 【重磅】百度开源分布式深度学习平台,挑战TensorFlow (教程)
- WordPress评论ajax动态加载,解决静态缓存下评论不更新问题
- WordPress显示访客UA信息:Show UserAgent纯代码轻度汉化版
- WordPress开启颜色评论但不造成XSS漏洞的小方法
- WordPress强迫症技巧:让文章(ID)地址完美连续(障眼法)
- iOS内存管理:从MRC到ARC实践
- MySQL错误修复:Table xx is marked as crashed and last (automatic?) repair failed
- PHP跨站脚本攻击(XSS)漏洞修复方法(一)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PAT (Advanced Level) Practice 1021 Deepest Root (25 分)
- ESlint + stylelint + VSCode自动格式化代码(2020)
- PAT (Basic Level) Practice (中文)1032 挖掘机技术哪家强 (20 分)
- PAT (Advanced Level) Practice 1023 Have Fun with Numbers (20 分)
- PAT (Basic Level) Practice (中文)1034 有理数四则运算 (20 分)
- 移动端开发的几点建议
- 数据结构题集(严书)图 常见习题代码
- PAT (Basic Level) Practice (中文)1036 跟奥巴马一起编程 (15 分)
- PAT (Advanced Level) Practice 1024 Palindromic Number (25 分)
- Flink 连接 hive 解决 java.net.UnknownHostException
- PAT (Advanced Level) Practice 1147 Heaps (30 分)
- Java自动化测试(app自动化环境搭建 31)
- PAT (Basic Level) Practice (中文)1038 统计同成绩学生 (20 分)
- 数据结构题集(严书)串 常见习题代码
- PAT (Basic Level) Practice (中文)1040 有几个PAT (25 分)