1. 程式人生 > 實用技巧 >「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;
}