1. 程式人生 > 其它 >[CTSC2018] 暴力寫掛

[CTSC2018] 暴力寫掛

Time for a lesson, insolent pup!

一、前言

震驚,這道題竟然是我的邊分樹入門題!感謝永神教我邊分樹!

前排警告:這是我沒借鑑任何題解,自己寫的程式碼,非常醜,如果你是為了看我程式碼而來的,小心為妙!

做這道題之前我甚至沒寫過一道邊分治。

二、題目

洛谷

LOJ

UOJ

三、講解

在瞭解邊分樹之前,我們先需要了解邊分治,如果你有點分治的基礎應該更好理解一些。

(一)、邊分治

點分治是每次選重心然後分治,而邊分治每次則是選一條邊,分治兩棵子樹。當點的度數為常數的時候邊分治的複雜度才能夠保證,比如菊花圖就很顯然可以卡死邊分治。

所以邊分治之前我們要先三度化整棵樹,具體操作是對於一個點 \(u\),每次取出一個兒子 \(v_i\),在新圖中連邊 \(u,v_i\)

,並新建虛點 \(t_i\),連邊 \(u,t_i\),然後將 \(t_i\) 作為新的 \(u\) 繼續取出剩下的兒子進行操作。

最後連出來大概每個點的兒子都是一個實點一個虛點,這個過程畫一下圖或者看看程式碼就懂了。

然後就可以用類似點分治的做法去找分治邊,打上標記,遞迴兩棵子樹。注意這條邊連的兩個點並不會打上標記,之後也會再次遇到。

用邊分治解決問題其實和點分治思路差不多,點分治是統計過分治點的路徑的貢獻,那麼邊分治就是統計過分治邊的路徑的貢獻。

(二)、邊分樹

邊分樹之於邊分治和點分樹之於點分樹完全不同!邊分樹是 \(n\) 條長 \(\log_2n\) 的鏈合併成的,類似線段樹合併,因此構建過程就是每個點都維護一條鏈。

考慮我們每次邊分治的時候會把整棵樹分成兩棵子樹,而分治邊相連的兩個點一定深度不同,我們記深度小的那個點所在的子樹為 \(T_1\),另一個為 \(T_2\),我們在 \(T_1\) 中每個點的鏈的底部加一個左兒子表示這個點在這一層分治中被分到了深度小的那邊,同樣的,我們把 \(T_2\) 中的每個點的鏈底部加一個右兒子。

為方便講述,我將這些將組成邊分樹的鏈叫做邊分鏈

我們在邊分鏈上儲存一些資訊就可以方便我們在之後的合併時快速求出我們想要的資訊,至於存啥,視題目而定。

才做一道題就下結論是吧。

(三)、正題

鋪墊了這麼多,終於進入這道題了。首先我們進行轉化(depth->d):

\(d(x)+d(y)-(d(LCA(x,y))+d'(LCA(x,y)))=\frac{1}{2}(d(x)+d(y)+dist(x,y)-2d'(LCA'(x,y)))\)

然後我們考慮在第二棵樹上列舉 \(LCA'(x,y)\),如果我們能快速求出前面的 \(d(x)+d(y)+dist(x,y)\) 的最大值那麼這道題就做出來了,考慮邊分治。

我們把 \(LCA'(x,y)\) 在第一棵樹上建出虛樹,然後用邊分治求出 \(d(x)+d(y)+dist(x,y)\) 的最大值,因為被分治邊分割後可以單獨求 \(d(x)+dist(x,u)\)\(d(y)+dist(y,v)\) 的最大值,然後加上分治邊的邊權 \(edgeval_{u,v}\) 即可。

但是每次都建樹我們顯然無法接受,所以這個時候我們要使用邊分樹。考慮兩個點產生貢獻的時候就是它們的邊分鏈第一次不同的時候,這個時候我們自然就發現邊分鏈要儲存到分治邊的距離以及分治邊的邊權,把兩個點的距離加起來再加上分治邊邊權即可得到 \(dist(x,y)\),而 \(d(x),d(y)\) 也可以當做它們自身的權值存到邊分鏈上。這樣我們就可以得到 \(d(x)+d(y)+dist(x,y)\) 了。

再回到之前的做法,我們什麼時候要知道哪些點之間的 \(d(x)+d(y)+dist(x,y)\) 呢?哈!是列舉 \(LCA'(x,y)\) 的時候,我們要知道它的子樹之間的 \(d(x)+d(y)+dist(x,y)\) 的最大值!沒錯,這個值可以用類似線段樹合併的做法得到!

總時間複雜度 \(O(n\log_2n)\),畢竟完全沒看網上的程式碼,寫的很醜,常數略大。

四、程式碼

作為資料結構題,程式碼還是挺短的。
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = (366666+5) << 1;
const LL INF = 1ll << 60;
int n,N;
LL ans;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int head[3][MAXN],tot = 1;
bool vis[MAXN<<2];
struct edge
{
	int v,w,nxt;
}e[MAXN<<2];
void Add_Edge(int opt,int u,int v,int w)
{
	e[++tot] = edge{v,w,head[opt][u]};
	head[opt][u] = tot;
}
void Add_Double_Edge(int opt,int u,int v,int w)
{
	Add_Edge(opt,u,v,w);
	Add_Edge(opt,v,u,w);
}

LL d[3][MAXN];
void dfs1(int x,int fa)
{
	int lst = x;
	for(int i = head[0][x],v; i ;i = e[i].nxt)//三度化
		if((v = e[i].v) ^ fa)
		{
			Add_Double_Edge(2,lst,++N,0);
			Add_Double_Edge(2,lst,v,e[i].w);
			d[2][N] = d[2][lst]; d[2][v] = d[2][lst] + e[i].w; d[0][v] = d[0][x] + e[i].w;
			lst = N;
			dfs1(v,x);
		}
}
struct node
{
	int ch[2];
	LL val,ev;
}t[MAXN*25];
pair<int,int> E;
int M,siz[MAXN],rt[MAXN],pos[MAXN],cnt;
void getedge(int x,int fa,int S)
{
	siz[x] = 1;
	for(int i = head[2][x],v,val; i ;i = e[i].nxt)
		if(!vis[i] && (v = e[i].v) != fa) 
		{
			getedge(v,x,S); siz[x] += siz[v];
			val = Max(siz[v],S-siz[v]);
			if(val < M) M = val,E = make_pair(x,i);
		}
}
LL edgeval;
void prepare(int x,int fa,bool wd,LL val)//getsize & build tree :)
{
	t[pos[x]].ch[wd] = ++cnt; pos[x] = cnt; siz[x] = 1;
	t[pos[x]].ev = edgeval; t[pos[x]].val = val+d[2][x];
	for(int i = head[2][x],v; i ;i = e[i].nxt)
		if(!vis[i] && (v = e[i].v) != fa) 
			prepare(v,x,wd,val+e[i].w),siz[x] += siz[v];
}
void dfs2(int x,int fa,LL val)
{
	edgeval = val;
	prepare(x,fa,d[2][x] != d[2][fa] ? d[2][x] > d[2][fa] : x > fa,0);
	if(siz[x] == 1) return;
	M = N; getedge(x,0,siz[x]);
	pair<int,int> now = E; vis[now.second] = vis[now.second^1] = 1;
	dfs2(now.first,e[now.second].v,e[now.second].w); dfs2(e[now.second].v,now.first,e[now.second].w);
}
void up(int x,int y,LL &MM)
{
	if(!x || !y) return;
	MM = Max(MM,t[x].val+t[y].val+t[x].ev);
	if(t[x].ev != t[y].ev) printf("Impossible!!!\n");
}
int mge(int x,int y,LL &MM)
{
	if(!x || !y) return x | y;
	up(t[x].ch[0],t[y].ch[1],MM); up(t[x].ch[1],t[y].ch[0],MM);
	t[x].ch[0] = mge(t[x].ch[0],t[y].ch[0],MM);
	t[x].ch[1] = mge(t[x].ch[1],t[y].ch[1],MM);
	t[x].val = Max(t[x].val,t[y].val);
	return x;
}
void print(int x) 
{
	printf("print %d %d %d %lld %lld\n",x,t[x].ch[0],t[x].ch[1],t[x].val,t[x].ev);
	if(t[x].ch[0]) print(t[x].ch[0]);
	if(t[x].ch[1]) print(t[x].ch[1]);
}

void dfs3(int x,int fa)
{
	LL tmpmax = -INF;//!!!
	for(int i = head[1][x],v; i ;i = e[i].nxt)
		if((v = e[i].v) ^ fa)
		{
			d[1][v] = d[1][x] + e[i].w,dfs3(v,x);
			rt[x] = mge(rt[x],rt[v],tmpmax);
		}
	ans = Max(ans,(tmpmax-(d[1][x] << 1)) >> 1);
}
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	N = n = Read();
	for(int opt = 0;opt < 2;++ opt)
		for(int i = 1,u,v;i < n;++ i) 
			u = Read(),v = Read(),Add_Double_Edge(opt,u,v,Read());
	dfs1(1,0); cnt = N;
	for(int i = 1;i <= N;++ i) rt[i] = pos[i] = i;
	M = N; getedge(1,0,N); 
	pair<int,int> now = E; vis[now.second] = vis[now.second^1] = 1;
	dfs2(now.first,e[now.second].v,e[now.second].w); dfs2(e[now.second].v,now.first,e[now.second].w);
	dfs3(1,0);
	for(int i = 1;i <= n;++ i) ans = Max(ans,d[0][i]-d[1][i]);
	Put(ans,'\n');
	return 0;
}