【题解】 「CTSC2018」暴力写挂 点分治+虚树+树形dp LOJ2553

Legend

Link \(\textrm{to LOJ}\)

给定两棵 \(n\) 个节点的树 \(T,T'\),边有边权 \(v_i,v'_i\)。定义两点 \((x,y)\) 之间的距离为:

\[dist(x,y)=dep_{x}+dep_{y}-(dep_{\operatorname{LCA}(x,y)} + dep'_{\operatorname{LCA}'(x,y)}) \]

\(\max dist(x,y)\)

\(n \le 366666\)\(|v_i|,|v_i'| \le 2017011328\)

Editorial

式子中有两个 \(\rm{LCA}\),让人很不爽。因为带有 \('\) 的只有后面一项,肯定去不掉,只能去掉前面一个:

\(dist(x,y)=\dfrac{1}{2}\left(dep_{x}+dep_{y}+w(x,y)-2dep'_{\operatorname{LCA}'(x,y)}\right)\)

这里的 \(w(x,y)\) 指的是树上两点真实距离。

请先无视那个 \(\dfrac{1}{2}\),思考怎么计算后面一坨。

不要被这个式子吓倒了,如果我们不考虑最后一项怎么做?

一个简单的树形 \(\rm{dp}\),假设你现在在 \(x\),对于每一个子节点 \(i\) 维护子树内的最大 \(a_i=dep_i+w(x,i)\)。然后选择不同的儿子进行合并答案就可以了。

具体来说就是,只要算出 \(\max a_i+a_j\ (i,j \in son_x ,i \not= j)\) 就行了。

这个显然可以 \(O(n)\) 做。

那么加入最后一项为我们带来了一些不便——我们不能通过树上的前缀差分同时快速拼凑出两棵树的贡献。

所以我们必然要对于某一棵树进行数据结构的维护。

最终我们选择了点分治来对树 \(T\) 进行分治,这样依然可以通过前缀差分快速算这一棵树对于答案式子的贡献

设当前分治中心是 \(x\),记录一个 \(v_i=w(i,x)+dep_i\),那么属于两棵不同子树的节点 \(i,j\) 的贡献就是 \(dist(i,j)=v_i+v_j-2dep'_{\operatorname{LCA}'(x,y)}\)

这时,在树 \(T\) 上的部分已经处理完了,被我们整合成了一个叫做 \(v_i\) 的信息。

那么接下来我们要算的是属于不同子树的节点两两之间在树 \(T'\) 上的贡献 \(-2dep'_{\operatorname{LCA}'(x,y)}\)

显然的想法是枚举 \(\rm{LCA}\),我们可以用树形 \(\rm{dp}\) 来实现:

假设你现在在 \(x\),维护子树内的最大和次大 \(v_i\),用 \(a_i,a'_i\) 表示,并要求它们不能属于同一棵(点分树的)子树(显然点分治在当前分治中心不能计算同一子树的贡献)。

答案就是 \(a_i+a'_i-2dep'_{x}\)

如果你把这个树形 \(\rm{dp}\) 搬到虚树上来的话,复杂度就变成了总体 \(O(n \log n)\),或总体 \(O(n)\)

加上点分治的复杂度,本题复杂度就是 \(O(n \log n^2)\) 或者 \(O(n \log n)\)

Code

我很懒,写的是 \(O(n \log^2 n)\) 的。

倍增的时候看清楚 \(i\)\(i+1\)……今天为了一个 \(\operatorname{LCA}\) 调试了一上午。。。。。

注意特判 \(x=y\) 的情况。

#include <bits/stdc++.h>

#define LL long long
#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)

const int MX = 366666 + 23;
int read(){
	char k = getchar(); int x = 0 ,flg = 1;
	while(k < '0' || k > '9') flg *= (k == '-' ? -1 : 1) ,k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x * flg;
}

LL Ans = LLONG_MIN;
int T1[MX] ,T2[MX] ,tot = 1;
struct edge{
	int node ,next ,w;
}h[MX << 2];
void addedge(int u ,int v ,int w ,int *head ,int flg = 1){
	h[++tot] = (edge){v ,head[u] ,w} ,head[u] = tot;
	if(flg) addedge(v ,u ,w ,head ,false);
}

void GetDepth(int x ,int f); // Get depth array of tree <1>
void GetRoot(int x ,int f);  // For vertex divivde & conquer 
void Stain(int x ,int f ,int col ,LL w); // For vertex divivde & conquer 
void Doit(int x); // For vertex divivde & conquer 
void DivAndCon(int x); // For vertex divivde & conquer 
void BuildVoidTree();
void GetOrder(int x ,int f); // Get order of node(first and last occurrences) tree<2>

namespace FOR_LCA{
	int lg2[MX << 1];
	// void GetFA(int x); // Get father array of Tree <2>
	int rmq(int x ,int y);
	int LCA(int x ,int y); // Get LCA(x ,y) of Tree <2>
	int dfn[MX] ,cnt ,seq[20][MX << 1] ,seqcnt ,refer[MX];
	int firstOccurrence[MX];
	void init_log2(){
		lg2[0] = -1;
		for(int i = 1 ; i < MX * 2 ; ++i)
			lg2[i] = lg2[i - 1] + (i == (i & -i));
		for(int i = 1 ; i <= 19 ; ++i){
			for(int j = 1 ; j + (1 << (i - 1)) - 1 <= seqcnt ; ++j){
				seq[i][j] = std::min(seq[i - 1][j] ,seq[i - 1][j + (1 << (i - 1))]);
			}
		}
	}
	void GetEuler(int x ,int f = 0){
		static int *head = T2;
		seq[0][++seqcnt] = dfn[x] = ++cnt;
		firstOccurrence[x] = seqcnt;
		refer[cnt] = x;
		for(int i = head[x] ,d ; i ; i = h[i].next){
			if((d = h[i].node) == f) continue;
			GetEuler(d ,x);
			seq[0][++seqcnt] = dfn[x];
		}
	}
	int rmq(int l ,int r){
		if(l > r) std::swap(l ,r);
		int len = lg2[r - l + 1];
		return std::min(seq[len][l] ,seq[len][r - (1 << len) + 1]);
	}
	int LCA(int l ,int r){
		return refer[rmq(firstOccurrence[l] ,firstOccurrence[r])];
	}
	void Main(){
		GetEuler(1);
		init_log2();
	}
}using FOR_LCA::LCA;

LL dep[MX];
void GetDepth(int x ,int f){
	static int *head = T1;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f) continue;
		dep[d] = dep[x] + h[i].w ,GetDepth(d ,x);
	}
}

int R ,Size[MX] ,mxsz[MX] ,TreeSize ,vis[MX];
void GetRoot(int x ,int f){
	static int *head = T1;
	Size[x] = 1 ,mxsz[x] = 0;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if(vis[d = h[i].node] || d == f) continue;
		GetRoot(d ,x) ,Size[x] += Size[d];
		mxsz[x] = std::max(mxsz[x] ,Size[d]);
	}
	mxsz[x] = std::max(mxsz[x] ,TreeSize - Size[x]);
	// debug("mxsz[%d] = %d\n" ,x ,mxsz[x]);
	if(mxsz[x] < mxsz[R]) R = x;
}

int color[MX];
LL val[MX];

struct POINT{
	int id ,tim;
	bool operator <(const POINT &B)const{
		return tim < B.tim;
	}
}S[MX << 1];

int app[2][MX] ,appcnt;
LL depT2[MX];
void GetOrder(int x ,int f){
	static int *head = T2;
	app[0][x] = ++appcnt;
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f) continue;
		depT2[d] = depT2[x] + h[i].w;
		GetOrder(d ,x);
	}
	app[1][x] = ++appcnt;
}

int Scnt ,Suse[MX];
void addPOINT(int x){
	if(Suse[x]++) return;
	++Scnt ,S[Scnt] = (POINT){x ,app[0][x]};
	++Scnt ,S[Scnt] = (POINT){-x ,app[1][x]};
}

struct DPclass{
	LL mx[2];
	int color[2];
	DPclass(){
		mx[0] = mx[1] = -(1LL << 50);
		color[0] = rand() ,color[1] = rand();
	}
	DPclass(LL mx0 ,LL color0){
		mx[0] = mx0 ,mx[1] = -(1LL << 50);
		color[0] = color0 ,color[1] = rand();
	}
	LL operator +(DPclass B)const{
		if(color[0] == B.color[0]){
			return std::max(mx[0] + B.mx[1] ,B.mx[0] + mx[1]);
		}
		return mx[0] + B.mx[0];
	}
	
	void output(){
		printf("{%lld ,%d}, {%lld ,%d}\n" ,mx[0] ,color[0] ,mx[1] ,color[1]);
	}
}dp[MX];

DPclass max(DPclass A ,DPclass B){
	DPclass C;
	if(A.color[0] == B.color[0]){
		C.color[0] = A.color[0];
		C.mx[0] = std::max(A.mx[0] ,B.mx[0]);
		C.mx[1] = std::max(A.mx[1] ,B.mx[1]);
		C.color[1] = (C.mx[1] == A.mx[1] ? A.color[1] : B.color[1]);
	}
	else{
		if(A.mx[1] >= B.mx[0]) return A;
		if(B.mx[1] >= A.mx[0]) return B;
		C.mx[0] = A.mx[0];
		C.color[0] = A.color[0];
		C.mx[1] = B.mx[0];
		C.color[1] = B.color[0];
		if(C.mx[0] < C.mx[1]){
			std::swap(C.mx[0] ,C.mx[1]);
			std::swap(C.color[0] ,C.color[1]);
		}
	}
	return C;
}

int stk[MX] ,stkcnt;
void BuildVoidTree(){
	std::sort(S + 1 ,S + 1 + Scnt);
	int curScnt = Scnt;
	for(int i = 1 ; i < curScnt ; ++i){
		// debug("LCA(%d ,%d) = %d\n" ,std::abs(S[i].id) ,std::abs(S[i + 1].id) ,LCA(std::abs(S[i].id) ,std::abs(S[i + 1].id)));
		int lca = LCA(std::abs(S[i].id) ,std::abs(S[i + 1].id));
		addPOINT(lca);
	}
	addPOINT(1);
	std::sort(S + 1 ,S + 1 + Scnt);
	for(int i = 1 ; i <= Scnt ; ++i){
		if(S[i].id > 0){
			int x = S[i].id;
			stk[++stkcnt] = x;
			if(color[x]){
				dp[x] = DPclass(val[x] ,color[x]);
			}
			else dp[x] = DPclass();
		}
		else{
			int x = stk[stkcnt--];
			if(stkcnt){
				Ans = std::max(Ans ,dp[x] + dp[stk[stkcnt]] - 2 * depT2[stk[stkcnt]]);
				dp[stk[stkcnt]] = max(dp[stk[stkcnt]] ,dp[x]);
			}
			Suse[x] = false;
			color[x] = 0;
		}
	}

	Scnt = 0;	
}

void Stain(int x ,int f ,int col ,LL w){
	addPOINT(x);
	static int *head = T1;
	color[x] = col;
	val[x] = w + dep[x];
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f || vis[d]) continue;
		Stain(d ,x ,col ,w + h[i].w);
	}
}

void Doit(int x){
	static int *head = T1;
	int color_cnt = 0;
	color[x] = ++color_cnt;
	val[x] = dep[x];
	addPOINT(x);
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if(vis[d = h[i].node]) continue;
		Stain(d ,x ,++color_cnt ,h[i].w);
	}
	BuildVoidTree();
}

void DivAndCon(int x){
	static int *head = T1;
	vis[x] = true;
	Doit(x);
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if(vis[d = h[i].node]) continue;
		mxsz[R = 0] = TreeSize = Size[d];
		GetRoot(d ,x);
		DivAndCon(R);
	}
}

int main(){

	__FILE([CTSC2018]暴力写挂);
	int n = read();
	for(int i = 1 ,u ,v ,w ; i < n ; ++i){
		u = read() ,v = read() ,w = read();
		addedge(u ,v ,w ,T1);
	}
	for(int i = 1 ,u ,v ,w ; i < n ; ++i){
		u = read() ,v = read() ,w = read();
		addedge(u ,v ,w ,T2);
	}

	// GetFA();

	FOR_LCA::Main();
	GetOrder(1 ,0);
	GetDepth(1 ,0);

	TreeSize = mxsz[R = 0] = n;
	GetRoot(1 ,0) ,DivAndCon(R);
	for(int i = 1 ; i <= n ; ++i){
		Ans = std::max(Ans ,2 * (dep[i] - depT2[i]));
	}
	printf("%lld\n" ,Ans / 2);
	return 0;
}
posted @ 2020-10-03 11:39  Imakf  阅读(210)  评论(0编辑  收藏  举报