1. 程式人生 > 實用技巧 >題解 「CTSC2018暴力寫掛」

題解 「CTSC2018暴力寫掛」

題目傳送門

題目大意

給出兩個大小為 \(n\) 的樹,求出:

\[\max\{\text{depth}(x)+\text{depth}(y)-\text{depth}(\text{LCA}(x,y)-\text{depth}^{'}(\text{LCA}^{'}(x,y)))\} \]

\(n\le 3666666\),答案保證在 \(\text{long long}\) 範圍內。

思路

邊分治秒啊,終於學會了 邊分樹合併 了,在這裡記錄一下,以免後面忘掉了。

首先我們可(bu)以(ke)想(neng)到(de)一種 \(\Theta(n\log^2 n)\) 的做法,就是說我們可以先把式子化成:

\[\frac{1}{2}(\text{dist}(x,y)+\text{depth}(x)+\text{depth}(y)-2\text{depth}^{'}(\text{LCA}^{'}(x,y))) \]

然後你發現如果不看最後那個東西的話,前面那個可以用邊分治解決。然後我們發現我們可以對於第二棵樹建出當前分治的點集的虛樹,然後列舉某一個點作為 \(\text{LCA}^{'}(x,y)\) 。然後你就發現時間複雜度是 \(\Theta(n\log^2 n)\) ,而且常數巨大,無法通過此題。

然後我們需要引入一個叫「邊分樹」的東西,它跟點分樹完全不一樣。對於一個點我們維護一個它每一次被分治到的貢獻,這道題目當中即為 \(\text{dis}(x)+\text{depth}(x)\)

\(\text{dis}(x)\) 即為 \(x\) 到分治點的距離),但是光是這樣還不夠,我們還需要能夠更新答案,也就是說我們需要知道每次分治所屬的塊,這就是為什麼需要是二叉樹鏈的原因,如果某個點是它父親的左節點,就說明他父親是左邊的塊(左右其實是自己定義的,但這並不是很重要)。

考慮合併兩個二叉樹,對於同一相同路徑走到的 \(x,y\) ,顯然它們可以合成一個合法答案,直接連起來即可。

考慮如何弄到第二棵樹上去,你發現如果欽定某一個點為 \(\text{LCA}\),那麼就可以用已有的點與子子樹進行合併更新答案即可,不過我們還需要一個點對它自己的貢獻。

時間複雜度是 \(\Theta(n\log n)\)

,但是常數比較大。

\(\texttt{Code}\)

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

#define Int register int
#define INF 0x7f7f7f7f7f
#define ll long long
#define MAXN 400250

char buf[1 << 25],*p1 = buf,*p2 = buf;
#define getchar() ((p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1 << 25,stdin))) ? EOF : *p1 ++)
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define vl(x) tree[x].vl
#define vr(x) tree[x].vr

struct node{
	ll vl,vr;
	int ls,rs;
	node(){vl = vr = -INF;}
}tree[MAXN * 25];

int n,cnt,root[MAXN],las[MAXN];

namespace T1{
#define N 800250
	ll dis[N],wei[N << 1];
	int lim,ed,tot,toop = 1,to[N << 1],nxt[N << 1],vis[N << 1],siz[N],head[N];
	void Add_Edge (int u,int v,ll w){
		to[++ toop] = v,wei[toop] = w,nxt[toop] = head[u],head[u] = toop;
		to[++ toop] = u,wei[toop] = w,nxt[toop] = head[v],head[v] = toop;
	}
	void getdis (int u,int fa){for (Int i = head[u];i;i = nxt[i]) if (to[i] ^ fa) dis[to[i]] = dis[u] + wei[i],getdis (to[i],u);}
	void findedge (int u,int fa,int Siz){
		siz[u] = 1;
		for (Int i = head[u];i;i = nxt[i]){
			int v = to[i];
			if (v == fa || vis[i]) continue;
			findedge (v,u,Siz),siz[u] += siz[v];
			int tmp = max (siz[v],Siz - siz[v]);
			if (tmp < lim) lim = tmp,ed = i;
		}
	}
	void dfs (int u,int fa,ll Dis,bool kase){
		if (u <= n){
			++ tot;
			if (ls(las[u]) == -1) ls(las[u]) = tot;
			else rs(las[u]) = tot;
			if (kase == 0) ls(tot) = -1,vl(tot) = Dis + dis[u];
			else rs(tot) = -1,vr(tot) = Dis + dis[u];
			las[u] = tot;
		}
		for (Int i = head[u];i;i = nxt[i]){
			int v = to[i];if (v == fa || vis[i]) continue;
			dfs (v,u,Dis + wei[i],kase);
		}
	}
	void divide (int u,int Siz){
		if (Siz <= 1) return ;
		lim = Siz,findedge (u,0,Siz),vis[ed] = vis[ed ^ 1] = 1;
		int tx = to[ed],ty = to[ed ^ 1];
		dfs (tx,0,0,0),dfs (ty,0,wei[ed],1);
		divide (ty,Siz - siz[tx]),divide (tx,siz[tx]);
	}
	void Work (){
		for (Int i = 1;i <= n;++ i) root[i] = las[i] = ++ tot,ls(root[i]) = -1;
		getdis (1,0),divide (1,cnt);
	}
#undef N
}

namespace T2{
	ll ans,now,wei[MAXN << 1];
	int toop = 1,to[MAXN << 1],nxt[MAXN << 1],head[MAXN];
	void Add_Edge (int u,int v,ll w){
		to[++ toop] = v,wei[toop] = w,nxt[toop] = head[u],head[u] = toop;
		to[++ toop] = u,wei[toop] = w,nxt[toop] = head[v],head[v] = toop;
	}
	int Merge (int x,int y){
		if (!x || !y) return x + y;
		ans = max (ans,max (vl(x) + vr(y),vr(x) + vl(y)) - now);
		ls(x) = Merge (ls(x),ls(y)),rs(x) = Merge (rs(x),rs(y));
		vl(x) = max (vl(x),vl(y)),vr(x) = max (vr(x),vr(y));
		return x;
	}
	void dfs (int u,int fa,ll dis){
		for (Int i = head[u];i;i = nxt[i]){
			int v = to[i];if (v == fa) continue;
			dfs (v,u,dis + wei[i]),now = dis * 2,root[u] = Merge (root[u],root[v]); 
		} 
		ans = max (ans,(T1::dis[u] - dis) * 2);
	}
	void Work (){
		dfs (1,0,0),write (ans / 2),putchar ('\n');
	}
}

#define N 800250

ll wei[N << 1];
int toop = 1,to[N << 1],nxt[N << 1],head[N];

void Add_Edge (int u,int v,ll w){
	to[++ toop] = v,wei[toop] = w,nxt[toop] = head[u],head[u] = toop;
	to[++ toop] = u,wei[toop] = w,nxt[toop] = head[v],head[v] = toop;
}

void dfs (int u,int fa){
	for (Int i = head[u];i;i = nxt[i]){
		int v = to[i];ll w = wei[i];
		if (v == fa) continue;
		dfs (v,u);
		if (!las[u]) T1::Add_Edge (u,v,w),las[u] = u;
		else T1::Add_Edge (las[u],++ cnt,0),T1::Add_Edge (las[u] = cnt,v,w);
	}
}

signed main(){
	read (n),cnt = n;
	for (Int i = 2,u,v,w;i <= n;++ i) read (u,v,w),Add_Edge (u,v,w);
	for (Int i = 2,u,v,w;i <= n;++ i) read (u,v,w),T2::Add_Edge (u,v,w);
	dfs (1,0),T1::Work ();T2::Work ();
	return 0;
}