「CF1000G Two-Paths」

时间:2020-10-16
本文章向大家介绍「CF1000G Two-Paths」,主要包括「CF1000G Two-Paths」使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目大意

给出一棵树,多次查询两个点之间的 \(\mathcal{Two-Path}\) 的权值的最大值,以下为 \(\mathcal{Two-Path}\) 路径权值的定义:

\[value=\sum\limits_{p\in path}a_p-\sum\limits_{e\in path}(w_ek_e) \]

(其中 \(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\) 的贡献的最大值,显然有如下转移方程:

\[f_i=\sum\limits_{u\in son_i}\max\{f_u-2*VAL+val_u,0\} \]

(\(VAL_u\) 表示 \(u\)\(u\) 的父亲这条边的边权,\(val_u\) 表示 \(u\) 节点的权值)

但是对于两个点查询的时候并不能把路径上所以的 \(f\) 加起来,如下图:

我们需要的是蓝色部分以及绿色部分的贡献,但是对于 \(4\) 号节点存的贡献是红色部分给它的,但实际应该是只能有绿色部分,也就是说要减去蓝色部分的贡献,再理解理解后就可以得到以下式子:

\[p=f_i+f_{fa}-{\color{red}\max\{f_i-2*VAL_i+val_{i},0\}} \]

(\(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=\max\{g_{fa}+f_{fa}+val_{fa}-2*VAL_{i}-\max\{f_i-2*VAL_i+val_{i},0\},0\} \]

(\(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;
}

$flag 上一页

上一篇:【JDBC第4章】操作BLOB类型字段

下一篇:已是最后一篇