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 前言 不会概率题的菜鸡博主做了一道概率题. 写完发现运行效率榜上的人都没有用心卡常数——矩阵怎么可以用数组 ...
随机推荐
- 通俗易懂的join、left join、right join、full join、cross join
内连接:列出与连接条件匹配的数据行(join\inner join) 外连接:两表合并,如有不相同的列,另外一个表显示null(left join\right join\full outer join ...
- asBroadcastStream
StreamSubscription sc = StreamSubscription(); Stream s = Stream(); sc.addStream(s); var bs = sc.stre ...
- stm32 USART_IT_IDLE中断 一帧数据
USART_IT_IDLE中断,是串口收到一帧数据后,发生的中断.也可以叫做一包数据 USART_IT_IDLE和USART_IT_RXNE区别 当接收到1个字节,会产生USART_IT_RXNE中断 ...
- iOS应用图片尺寸制作脚本
1.前提说明 通常 2.代码使用说明 2.1 脚本基本代码 #!/bin/sh iPhoneIcon() { sips -z 30 25 if_connected_green.png --out ./ ...
- SAP云平台里的三叉戟应用
大家第一次看到SAP MTA这个词组,会联想到什么? Jerry第一次看到的时候,联想到的是那一个个足坛著名的三叉戟攻击组合. 海皇波塞冬(Poseidon),奥林匹斯十二神中地位仅次于宙斯的大神,海 ...
- javascript 四舍五入; js 四舍五入
方法 Math.round round() 方法可把一个数字舍入为最接近的整数. 对于 0.5,该方法将进行上舍入. 例如,3.5 将舍入为 4,而 -3.5 将舍入为 -3. Math.round( ...
- Python学习日记(八) 函数
函数的结构: 函数的返回值: 1.当函数执行时运到return关键字将不再往下执行 def func(): print("hello world!") print("he ...
- Python学习日记(七) 文件操作
文件操作: 首先要有一个文件作为对象,例‘文件名’.txt 1.文件路径:例 d:\文件名.txt <1>:绝对路径:从根目录往后的路径 <2>:相对路径:当前目录下的路径有什 ...
- 学习python的日常4
偏函数: 偏函数是functools模块提供的一个功能,偏函数可以通过设定参数的默认值,降低函数调用的难度 其中设定的参数默认值实际上是可以被传入为其他值的,最后创建偏函数时可接收函数对象.*args ...
- Django图书管理系统(前端对有外键的数据表增删改查)
图书管理 书籍管理 book name 项目源码位置:https://gitee.com/machangwei-8/learning_materials/tree/master/%E9%A1%B9%E ...