UOJ#470. 【ZJOI2019】语言 虚树,线段树合并
原文链接www.cnblogs.com/zhouzhendong/p/UOJ470.html
前言
做完情报中心来看这个题突然发现两题有相似之处然后就会做了。
题解
首先,我们考虑将所有答案点对分为两类。
- 一个节点对其祖先的贡献。
 - 来自一个节点的不同子树之间节点的贡献。
 
第一种情况非常简单,这里不加赘述。
对于第二种情况,我们首先考虑简单做法:
考虑对于每一个节点分开处理。
按照某一种顺序枚举它的子树,对于所有“一端在当前子树内,另一端在当前子树之前的子树”的路径,我们求它们的贡献。
接下来提到的“虚树“中默认加入当前节点。
考虑对当前子树内路径端点建立虚树,然后在虚树上 dfs。对于虚树上的一个节点,它在另外一个子树中有相同语言的节点就是它在虚树上的子树中的所有端点的另一端点构成的虚树大小。
一个节点的子树中所有端点对应的点构成的虚树可以由儿子节点的虚树合并而来。
如果事先将虚树内的节点存在 set 中,则可以在关于点数较少的虚树的复杂度内合并两棵虚树,具体地说是 size * log(n) 。
考虑使用 DSU on tree,我们可以得到一个 \(O(n\log ^ 3n)\) 的做法。
注意到,在很多问题里,线段树合并都可以处理树上启发式合并的问题,而且复杂度都会下降。这里也类似,考虑合并两个 dfs序 分别独立的虚树时,只需要特殊考虑 dfs序 小的虚树的 dfs序最大节点和 dfs序 大的虚树的dfs序最小节点到根的路径交即可。
于是,我们考虑采用线段树合并维护子树虚树 size,由于线段树合并中需要求 LCA,所以我们考虑用 ST表 来求 LCA,做到单次询问 \(O(1)\),即可得到一个总时间复杂度 \(O((n+m)\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++)
#define Fod(i,b,a) for (int i=(b);i>=(a);i--)
#define fi first
#define se second
#define pb(x) push_back(x)
#define mp(x,y) make_pair(x,y)
#define outval(x) cerr<<#x" = "<<x<<endl
#define outtag(x) cerr<<"---------------"#x"---------------"<<endl
#define outarr(a,L,R) cerr<<#a"["<<L<<".."<<R<<"] = ";\
						For(_x,L,R)cerr<<a[_x]<<" ";cerr<<endl;
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=100005*2;
int n,m;
vector <int> e[N];
struct cha{
	int x,y,lca;
	int xf,yf;
}a[N];
int depth[N],fa[N][20];
int ett[N],c=0,I[N];
void dfs(int x,int pre,int d){
	depth[x]=d,fa[x][0]=pre;
	For(i,1,19)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	ett[I[x]=++c]=x;
	for (int y : e[x])
		if (y!=pre)
			dfs(y,x,d+1),ett[++c]=x;
}
int st[N][20],Log[N];
int min_dep(int x,int y){
	return depth[x]<depth[y]?x:y;
}
void Get_ST(){
	For(i,2,c)
		Log[i]=Log[i>>1]+1;
	For(i,1,c){
		st[i][0]=ett[i];
		For(j,1,19){
			st[i][j]=st[i][j-1];
			if (i-(1<<(j-1))>0)
				st[i][j]=min_dep(st[i][j],st[i-(1<<(j-1))][j-1]);
		}
	}
}
int LCA(int x,int y){
	x=I[x],y=I[y];
	if (x>y)
		swap(x,y);
	int d=Log[y-x+1];
	return min_dep(st[x+(1<<d)-1][d],st[y][d]);
}
int Dis(int x,int y){
	return depth[x]+depth[y]-2*depth[LCA(x,y)];
}
namespace Seg{
	const int S=N*20*2;
	int sz[S],lp[S],rp[S],ls[S],rs[S];
	int cnt=0;
	void pushup(int rt){
		if (!sz[ls[rt]]&&!sz[rs[rt]])
			sz[rt]=lp[rt]=rp[rt]=0;
		else if (!sz[rs[rt]])
			sz[rt]=sz[ls[rt]],lp[rt]=lp[ls[rt]],rp[rt]=rp[ls[rt]];
		else if (!sz[ls[rt]])
			sz[rt]=sz[rs[rt]],lp[rt]=lp[rs[rt]],rp[rt]=rp[rs[rt]];
		else {
			sz[rt]=sz[ls[rt]]+sz[rs[rt]]-depth[LCA(rp[ls[rt]],lp[rs[rt]])];
			lp[rt]=lp[ls[rt]],rp[rt]=rp[rs[rt]];
		}
	}
	void Ins(int &rt,int L,int R,int x){
		if (!rt)
			rt=++cnt,sz[rt]=ls[rt]=rs[rt]=lp[rt]=rp[rt]=0;
		if (L==R){
			lp[rt]=rp[rt]=x,sz[rt]=depth[x];
			return;
		}
		int mid=(L+R)>>1;
		if (I[x]<=mid)
			Ins(ls[rt],L,mid,x);
		else
			Ins(rs[rt],mid+1,R,x);
		pushup(rt);
	}
	int Merge(int x,int y,int L,int R){
		if (!x||!y)
			return x|y;
		if (L==R)
			return x;
		int mid=(L+R)>>1,rt=++cnt;
		ls[rt]=Merge(ls[x],ls[y],L,mid);
		rs[rt]=Merge(rs[x],rs[y],mid+1,R);
		pushup(rt);
		return rt;
	}
}
int go_son(int x,int f){
	Fod(i,19,0)
		if (depth[x]-(1<<i)>depth[f])
			x=fa[x][i];
	return x;
}
LL ans=0;
vector <int> qid[N];
int up[N];
bool cmp_qid(int x,int y){
	return I[a[x].xf]<I[a[y].xf];
}
bool cmpI(int x,int y){
	return I[x]<I[y];
}
int rt[N];
void Solve(int x,int *id,int n){
	static int t[N],st[N];
	int tc=0,top=0;
	For(i,0,n-1)
		t[++tc]=a[id[i]].x;
	t[++tc]=x;
	sort(t+1,t+tc+1,cmpI);
	tc=unique(t+1,t+tc+1)-t-1;
	For(i,1,tc)
		rt[t[i]]=0;
	For(i,0,n-1)
		Seg::Ins(rt[a[id[i]].x],1,c,a[id[i]].y);
	For(_,1,tc){
		int i=t[_];
		if (top){
			int lca=LCA(i,st[top]);
			while (depth[st[top]]>depth[lca]){
				int now=st[top];
				if (depth[st[top-1]]>=depth[lca]){
					ans+=(LL)(depth[now]-depth[st[top-1]])*(Seg::sz[rt[now]]-depth[x]);
					rt[st[top-1]]=Seg::Merge(rt[st[top-1]],rt[now],1,c);
					top--;
				}
				else {
					ans+=(LL)(depth[now]-depth[lca])*(Seg::sz[rt[now]]-depth[x]);
					rt[lca]=rt[now];
					st[top]=lca;
					break;
				}
			}
		}
		st[++top]=i;
	}
	while (top>1){
		int now=st[top];
		ans+=(LL)(depth[now]-depth[st[top-1]])*(Seg::sz[rt[now]]-depth[x]);
		rt[st[top-1]]=Seg::Merge(rt[st[top-1]],rt[now],1,c);
		top--;
	}
}
void Solve(int x,int pre){
	for (int y : e[x])
		if (y!=pre)
			Solve(y,x),up[x]=max(up[x],up[y]-1);
	ans+=up[x];
	sort(qid[x].begin(),qid[x].end(),cmp_qid);
	int s=(int)qid[x].size();
	for (int i=0,j;i<s;i=j+1){
		for (j=i;j+1<s&&I[a[qid[x][i]].xf]==I[a[qid[x][j+1]].xf];j++);
		Solve(x,&qid[x][i],j-i+1);
	}
}
int main(){
	n=read(),m=read();
	For(i,1,n-1){
		int x=read(),y=read();
		e[x].pb(y),e[y].pb(x);
	}
	dfs(1,0,0);
	Get_ST();
	For(i,1,m){
		int x=a[i].x=read(),y=a[i].y=read(),lca=a[i].lca=LCA(x,y);
		up[x]=max(up[x],depth[x]-depth[lca]);
		up[y]=max(up[y],depth[y]-depth[lca]);
		if (x!=lca&&y!=lca){
			a[i].xf=go_son(x,lca);
			a[i].yf=go_son(y,lca);
			if (I[a[i].xf]<I[a[i].yf])
				swap(a[i].xf,a[i].yf),swap(a[i].x,a[i].y);
			qid[lca].pb(i);
		}
	}
	Solve(1,0);
	cout<<ans<<endl;
	return 0;
}
												
											UOJ#470. 【ZJOI2019】语言 虚树,线段树合并的更多相关文章
- [Luogu5327][ZJOI2019]语言(树上差分+线段树合并)
		
首先可以想到对每个点统计出所有经过它的链的并所包含的点数,然后可以直接得到答案.根据实现不同有下面几种方法.三个log:假如对每个点都存下经过它的链并S[x],那么每新加一条路径进来的时候,相当于在路 ...
 - UOJ #164 [清华集训2015]V (线段树)
		
题目链接 http://uoj.ac/problem/164 题解 神仙线段树题. 首先赋值操作可以等价于减掉正无穷再加上\(x\). 假设某个位置从前到后的操作序列是: \(x_1,x_2,..., ...
 - 浅谈树套树(线段树套平衡树)&学习笔记
		
0XFF 前言 *如果本文有不好的地方,请在下方评论区提出,Qiuly感激不尽! 0X1F 这个东西有啥用? 树套树------线段树套平衡树,可以用于解决待修改区间\(K\)大的问题,当然也可以用 ...
 - UOJ#467. 【ZJOI2019】线段树  线段树,概率期望
		
原文链接www.cnblogs.com/zhouzhendong/p/ZJOI2019Day1T2.html 前言 在LOJ交了一下我的代码,发现它比选手机快将近 4 倍. 题解 对于线段树上每一个节 ...
 - UOJ#7. 【NOI2014】购票 | 线段树 凸包优化DP
		
题目链接 UOJ #7 题解 首先这一定是DP!可以写出: \[f[i] = \min_{ancestor\ j} \{f[j] + (d[j] - d[i]) * p[i] + q[i]\}\] 其 ...
 - UOJ #314. 【NOI2017】整数 | 线段树 压位
		
题目链接 UOJ 134 题解 可爱的电音之王松松松出的题--好妙啊. 首先想一个朴素的做法! 把当前的整数的二进制当作01序列用线段树维护一下(序列的第i位就是整数中位权为\(2^k\)的那一位). ...
 - LOJ#3043.【ZJOI2019】 线段树  线段树,概率期望
		
原文链接www.cnblogs.com/zhouzhendong/p/ZJOI2019Day1T2.html 前言 在LOJ交了一下我的代码,发现它比选手机快将近 4 倍. 题解 对于线段树上每一个节 ...
 - 【uoj#228】基础数据结构练习题  线段树+均摊分析
		
题目描述 给出一个长度为 $n$ 的序列,支持 $m$ 次操作,操作有三种:区间加.区间开根.区间求和. $n,m,a_i\le 100000$ . 题解 线段树+均摊分析 对于原来的两个数 $a$ ...
 - UOJ#299. 【CTSC2017】游戏 线段树 概率期望 矩阵
		
原文链接www.cnblogs.com/zhouzhendong/p/UOJ299.html 前言 不会概率题的菜鸡博主做了一道概率题. 写完发现运行效率榜上的人都没有用心卡常数——矩阵怎么可以用数组 ...
 
随机推荐
- Windows server 2012 R2下安装sharepoint2013
			
• 安装windows server 2012 R2 系统,配置IP.系统打补丁,修改主机名.加域后重启.• 安装WEB服务器,勾选windows身份验证 • 安装应用程序服务器 • 安装.NET F ...
 - 初始SQL语句 简单使用
			
初始SQL语句 简单使用 SQL语言共分为四大类: DQL (Data QueryLanguage )数据查询语言 DML(Data manipulation language)数据操纵语言 DDL( ...
 - 一个时间O(n)的洗牌算法
			
//一种O(n)的洗牌算法 vector<int> randNUms(vector<int> &nums, int m) { int len = nums.size() ...
 - 卷积神经网络快速入门【基于TensorFlow】
			
一.概述 卷积神经网络[Convolutional neural networks]里面最重要的构建单元是卷积层.神经元在第一个卷积层不是连接输入图片的每一个像素,只是连接它们感受野1的像素,以此类推 ...
 - redis 异常 MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk
			
MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. 解决方 ...
 - python(列表函数)
			
一.列表函数 1.sort()原址排序 参数默认reverse=False时为正序排序 list1 = [1,3,5,2,1,23,18] list1.sort() print (list1) 当参数 ...
 - layui.js---layer;;前端预览pdf
			
layui.js---layer;;前端预览pdf 1.必须引入layui.js 2.uul是pdf文件地址 3.触发function函数:小于号button onclick="pdfsee ...
 - PyCharm-安装&调试
			
windows安装pycharm 和python的链接: PyCharm:http://www.jetbrains.com/pycharm/ Python:https://www.python.org ...
 - Win10 C盘 系统和保留 占用空间 非常大
			
Win10 C盘 系统和保留 占用空间 非常大今天在写代码的时候,突然发现Redis起不来了,一看原因,是因为C盘空间不足.然后,我看了下C盘,发现...一个叫系统和保留的东西,居然占了110G的空间 ...
 - (Linux基础学习)第七章:echo命令
			
第1节:简单说明功能:显示字符语法:echo [-neE][字符串]说明:echo会将输入的字符串送往标准输出.输出的字符串之间以空白字符隔开,并在最后加上换行号选项:-E(默认)不支持\解释功能-n ...