[CTSC2018] 暴力写挂

2022/2/18 23:19:09

本文主要是介绍[CTSC2018] 暴力写挂,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、前言

震惊,这道题竟然是我的边分树入门题!感谢永神教我边分树!

前排警告:这是我没借鉴任何题解,自己写的代码,非常丑,如果你是为了看我代码而来的,小心为妙!

做这道题之前我甚至没写过一道边分治。

二、题目

洛谷

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


这篇关于[CTSC2018] 暴力写挂的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程