UOJ#400. 【CTSC2018】暴力写挂 边分治 线段树合并

原文链接 www.cnblogs.com/zhouzhendong/p/UOJ400.html

前言

老年选手没有码力。

题解

先对第一棵树进行边分治,然后,设点 x 到分治中心的距离为 $D[x]$,点 x 在原树上的深度为 $d[x]$,那么

$$d[x]+d[y] - d[LCA(x,y)] - d‘[LCA(x,y)] = \frac 12(D[x] + d[x]) + \frac 12 (D[y] + d[y]) - d‘[LCA(x,y)]$$

于是我们考虑将分治区域内的节点在第二棵树上建虚树,并 DFS,每次维护一下子树中的 max(D[x] + d[x]) ,合并到父亲时顺便算一下答案。

类似于WC2018通道,这样做的时间复杂度是可以强行优化成 $O(n\log n)$ 的。

但是本题有更巧妙的做法。

考虑边分树这个数据结构。它具有几个性质:

1. 深度为 $O(\log n)$,准确地说是 $2\log_3 n$,略大于 $log _2 n $ 。(嗯对,xza深度只开了20,被我hack了\kel)

2. 叶子节点个数为 $n$ 。

如果任取一种 DFS 序,并将其叶子按顺序排列,那么,两组节点的边分树合并的过程就可以看做以叶子 DFS 序为定义域的线段树合并。时间复杂度证明和线段树合并相同。写法也几乎相同。

于是,我们得到下面的优秀算法:

首先对第一棵树进行边分治,建出边分树。

然后对第二棵树进行 DFS,用“边分树合并”来支持子树合并操作。在边分树合并的同时计算答案。 我们要维护的值仅仅是边分时两半集合中节点的权值 max 。

时间复杂度 $O(n\log n)$ 。

代码

#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
#define For(i,a,b) for (int i=a;i<=b;i++)
using namespace std;
typedef long long LL;
LL read(){
	LL x=0,f=0;
	char ch=getchar();
	while (!isdigit(ch))
		f|=ch==‘-‘,ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int N=366677*2,S=N*13;
const LL LLINF=1e18;
struct Graph{
	static const int M=N*2;
	int cnt,y[M],fst[N],nxt[M],z[M];
	void clear(){
		cnt=1,clr(fst);
	}
	void add(int a,int b,int c){
		y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt,z[cnt]=c;
	}
	void Add(int a,int b,int c){
		add(a,b,c),add(b,a,c);
	}
}t1,t2,T1;
#define Forg(g) for (int E=g.fst[x],y=g.y[E];E;E=g.nxt[E],y=g.y[E]) if (y!=pre)
int n,vcnt,ec;
int td[N],vis[N],pos[N];
LL d[N],val[27][N];
void GetT1(int x,int pre,LL D){
	int t=x,v;
	d[x]=D;
	Forg(t1)
		d[v=++vcnt]=D,T1.Add(t,v,0),T1.Add(v,y,t1.z[E]),t=v,GetT1(y,x,D+t1.z[E]);
}
namespace DT{
	int size[N],Mx[N],nowsize,kx,ky,kz;
	void GetRT(int x,int pre,int flen){
		size[x]=1;
		Forg(T1)
			if (!vis[y])
				GetRT(y,x,T1.z[E]),size[x]+=size[y];
		Mx[x]=max(size[x],nowsize-size[x]);
		if (!kx||Mx[x]<Mx[kx])
			kx=x,ky=pre,kz=flen;
	}
	void dfs(int x,int pre,LL D,int w){
		pos[x]|=w<<td[x],val[td[x]++][x]=D+d[x];
		Forg(T1)
			if (!vis[y])
				dfs(y,x,D+T1.z[E],w);
	}
	void Divide(int x,int Size){
		if (Size>1){
			kx=0,nowsize=Size,GetRT(x,0,0),x=kx;
			int y=ky,z=kz,tmp=size[x];
			dfs(x,y,0,0),dfs(y,x,z,1);
			vis[y]=1,Divide(x,tmp),vis[y]=0;
			vis[x]=1,Divide(y,Size-tmp);
		}
	}
}
int rt[N],ls[S],rs[S];
LL Lmx[S],Rmx[S],ans=-LLINF;
int st[S],top,cnt=0;
int NewNode(){
	return top?st[top--]:++cnt;
}
void RecNode(int x){
	ls[x]=rs[x]=Lmx[x]=Rmx[x]=0,st[++top]=x;
}
void Ins(int &rt,int x,int D){
	if (D<td[x]){
		rt=NewNode(),Lmx[rt]=Rmx[rt]=-LLINF;
		if (pos[x]>>D&1)
			Ins(rs[rt],x,D+1),Rmx[rt]=max(Rmx[rt],val[D][x]);
		else
			Ins(ls[rt],x,D+1),Lmx[rt]=max(Lmx[rt],val[D][x]);
	}
}
int Merge(int x,int y,LL add){
	if (!x||!y)
		return x|y;
	ans=max(ans,max(Lmx[x]+Rmx[y],Rmx[x]+Lmx[y])/2+add);
	Lmx[x]=max(Lmx[x],Lmx[y]),Rmx[x]=max(Rmx[x],Rmx[y]);
	ls[x]=Merge(ls[x],ls[y],add),rs[x]=Merge(rs[x],rs[y],add);
	return RecNode(y),x;
}
void Solve(int x,int pre,LL D){
	ans=max(ans,d[x]-D),Ins(rt[x],x,0);
	Forg(t2)
		Solve(y,x,D+t2.z[E]),rt[x]=Merge(rt[x],rt[y],-D);
}
int main(){
	n=read(),t1.clear(),t2.clear(),T1.clear();
	For(i,1,n-1){
		int x=read(),y=read(),z=read();
		t1.Add(x,y,z);
	}
	For(i,1,n-1){
		int x=read(),y=read(),z=read();
		t2.Add(x,y,z);
	}
	vcnt=ec=n,GetT1(1,0,0),DT::Divide(1,vcnt),Solve(1,0,0);
	cout<<ans<<endl;
	return 0;
}

  

原文地址:https://www.cnblogs.com/zhouzhendong/p/UOJ400.html

时间: 2024-08-30 10:57:09

UOJ#400. 【CTSC2018】暴力写挂 边分治 线段树合并的相关文章

CTSC2018 暴力写挂

CTSC2018 暴力写挂 题意: 题目传送门 题解: emm--第一次写边分治-- 考虑到第二棵树上的\(Lca\)我们难以处理,于是我们可以考虑枚举第二棵树上的\(Lca\),然后在第一棵树上最大化\(dep_u + dep_v - dep_{lca}\).但是在这个式子中,又受到了第一棵树上\(Lca\)的限制,于是我们考虑化简式子.稍微化简一下会发现式子变成了\(\frac{1}{2} * (dep_u + dep_v + dis_{u, v})\),然后我们就可以将其转化成无根树来做了

[CTSC2018]暴力写挂——边分树合并

[CTSC2018]暴力写挂 题面不错 给定两棵树,两点“距离”定义为:二者深度相加,减去两棵树上的LCA的深度(深度指到根节点的距离) 求最大的距离. 解决多棵树的问题就是降维了. 经典的做法是边分树合并. 边分树结构类似0/1 trie 就是把边分树对于每个点拆开路径 合并两棵边分树同时可以得到两个边分树之间点对的路径的信息 感觉有点类似线段树合并. 根据“猫树”思想,两点间的路径一定经过边分树上LCA的那条边.(u,v不相等) 我们考虑在这个LCA处统计贡献 具体地,先对1树进行边分治 每

[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度. 题解 这题的描述很短,给人一种很可做的假象. 暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq. 暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在pa

Loj #2553. 「CTSC2018」暴力写挂

Loj #2553. 「CTSC2018」暴力写挂 题目描述 temporaryDO 是一个很菜的 OIer .在 4 月,他在省队选拔赛的考场上见到了<林克卡特树>一题,其中 \(k = 0\) 的部分分是求树 \(T\) 上的最长链.可怜的 temporaryDO 并不会做这道题,他在考场上抓猫耳挠猫腮都想不出一点思路. 这时,善良的板板出现在了空中,他的身上发出璀璨却柔和的光芒,荡漾在考场上.''题目并不难.'' 板板说.那充满磁性的声音,让 temporaryDO 全身充满了力量. 他

hdu 4366 Successor - CDQ分治 - 线段树 - 树分块

Sean owns a company and he is the BOSS.The other Staff has one Superior.every staff has a loyalty and ability.Some times Sean will fire one staff.Then one of the fired man’s Subordinates will replace him whose ability is higher than him and has the h

UVALive 7148 LRIP【树分治+线段树】

题意就是要求一棵树上的最长不下降序列,同时不下降序列的最小值与最大值不超过D. 做法是树分治+线段树,假设树根是x,y是其当前需要处理的子树,对于子树y,需要处理出两个数组MN,MX,MN[i]表示以x为第一个数字的不下降子序列中第i个数的最小值,MX[i]表示以x为第一个数字的不上升子序列中第i个数的最大值.如果当前子树有一个以x为首的不下降序列,那么我们就需要在之前处理的子树中找一条以x为首的满足约束条件不上升序列,可以用线段树来查询.同时每做完一颗子树的时候,用MN,MX对线段树进行更新.

【BZOJ4372】烁烁的游戏 动态树分治+线段树

[BZOJ4372]烁烁的游戏 Description 背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠.题意:给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠.烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮鼠.皮皮鼠会被烁烁吸引,所以会一直待在节点上不动.烁烁很好奇,在当前时刻,节点u有多少个他的好朋友---皮皮鼠.大意:给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:Q x:询问x的点权.M x d w:将树上与节点x距离不超过d的节点的点权均加上w. In

uoj#400. 【CTSC2018】暴力写挂(边分治)

传送门 做一道题学一堆东西.jpg 猫老师的题--暴力拿的分好像比打挂的正解多很多啊--我纯暴力+部分分已经能有80了--正解没调对之前一直只有10分→_→ 先说一下什么是边分治.这个其实类似于点分治,不过分治对象从点换成边了,就是每次找到一条边,使其断开之后的两个连通块中最大的最小 于是我们就可以--等会儿如果在菊花图上怎么办?不是得卡到\(O(n^2)\)了? 不难发现这个东西的复杂度和节点的度数有关,于是为了假装这个东西能用避免这些情况,我们要把图给重构喽 简单来说就是通过加入虚点,把图给

ACdream1157 Segments(CDQ分治 + 线段树)

题目这么说的: 进行如下3种类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段,保证每条插入线段最多插入一次,且这次删除操作一定合法3) Q L R(1 <= L <= R <= 1000000000) 查询目前存在的线段中有多少条线段完全包含[L,R]这个线段,线段X被线段Y完全包含即LY <= LX <= RX <= RY) 初学CDQ分治是看了B