P5327 - 语言 题解
究极巨大神仙题。。。。(刚学线段树合并就做这么难的题啊?——小 c)
我们知道序列数点对一般离不开两种方法:cdq 分治和扫描线(就是对每个 \(r\) 数有多少个 \(l\))。对应到树上的话,cdq 分治就是点分治,扫描线变异出两种:枚举 lca 和枚举点对中的一个点。这题我们采用第三种方法。
点 \(x\) 可以开展贸易活动的点显然是所有经过 \(x\) 的 \((u,v)\) 的并。我们考虑对每个点都维护一个数据结构维护 \((u,v)\) 们的并,这样对每个 \((u,v)\) 我们都要在所有 \(x\in(u,v)\) 的数据结构上做 \((u,v)\) 链加。考虑树上差分,这样问题转化为对该数据结构做一遍子树和。那么容易想到可以用线段树合并维护,此时由于有链加,要树剖,总复杂度 2log。而且线段树合并上还要打懒标记 / 标记永久化,比较麻烦。
考虑优化。容易发现,对每个 \(x\),包含 \(x\) 的 \((u,v)\) 的并是一个包含 \(x\) 的连通块。感性理解发现其实就是所有 \(u,v\) 的虚树。证明(考场上就不证明了吧,太显然了):考虑两个 \((u_1,v_1),(u_2,v_2)\),它们都包含 \(x\),必然相交。设交为 \((u_3,v_3)\),不妨设 \(u_1,u_2\) 靠近 \(u_3\),\(v_1,v_2\) 靠近 \(v_3\),那么显然 \((u_1,u_2)=(u_1,u_3)\cup(u_2,u_3)\subseteq(u_1,v_1)\cup(u_2,v_2)\),\((v_1,v_2),(u_1,v_2),(u_2,v_1)\) 同理。所以说所有 \((u,v)\) 的并其实就是所有 \(u,v\) 两两之间的路径并,那么显然就是所有 \(u,v\) 的虚树。
我们现在想知道虚树的大小。这是一个很经典的 trick(参考 P3320)——动态维护虚树大小。解决这个问题并不需要把虚树建出来、把边连起来,只要想象模拟 dfs 的过程,将点们按 dfn 排序得到 \(v\),那么所有 \(v_i\to v_{i\bmod |v|+1}\) 会恰好把虚树的每条边访问两遍。所以我们只需要求相邻点的距离和即可。
现在我们想知道 \(x\) 的若干子树内的 \(u,v\) 集合并起来之后(顺便加入 \(x\) 的 todo-list),相邻点距离和等于多少。考虑线段树合并,区间维护 dfn 落在该区间内的相邻点距离和,上传只需要再维护区间内最左和最右点。线段树合并只要考虑在一满一空时和两叶子是是否可行(其他时候上传),前者直接移植,后者直接搞。总复杂度 1log。注意叶子如果被累加多次要真的累加多次(反正不影响答案),因为有减法操作。
code 还挺好写的
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=200010,LOG_N=20;
int n,m;
vector<int> nei[N];
vector<int> add[N],del[N];
int fa[N];
int euler[N],fst[N],noweuler,dep[N],dfn[N],nowdfn,mng[N];
void dfs(int x=1){
euler[++noweuler]=x;fst[x]=noweuler;
dfn[x]=++nowdfn;mng[nowdfn]=x;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(y==fa[x])continue;
dep[y]=dep[x]+1;
fa[y]=x;
dfs(y);
euler[++noweuler]=x;
}
}
bool cmp(int x,int y){return dep[x]<dep[y];}
struct stable{
int _log[N],mn[N][LOG_N];
void init(){
for(int i=2;i<=2*n;i++)_log[i]=_log[i-1]+(1<<_log[i-1]+1==i);
for(int i=1;i<2*n;i++)mn[i][0]=euler[i];
for(int j=1;j<LOG_N;j++)for(int i=1;i+(1<<j)-1<2*n;i++)mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1],cmp);
}
int _mn(int l,int r){
int log0=_log[r-l+1];
return min(mn[l][log0],mn[r-(1<<log0)+1][log0],cmp);
}
}st;
int lca(int x,int y){
x=fst[x],y=fst[y];
if(x>y)swap(x,y);
return st._mn(x,y);
}
int dis(int x,int y){
x=mng[x],y=mng[y];
int a=lca(x,y);
return dep[x]+dep[y]-2*dep[a];
}
struct segtree{
int sz,root[N];
struct node{int lson,rson,cnt,sum,lft,rit;}nd[N<<5];
#define lson(p) nd[p].lson
#define rson(p) nd[p].rson
#define cnt(p) nd[p].cnt
#define sum(p) nd[p].sum
#define lft(p) nd[p].lft
#define rit(p) nd[p].rit
int nwnd(){return nd[++sz]=node({0,0,0,0,0,0}),sz;}
void init(){
sz=0,nd[0]=node({0,0,0,0,0,0});
for(int i=1;i<=n;i++)root[i]=nwnd();
}
void sprup(int p){
sum(p)=sum(lson(p))+sum(rson(p))+(rit(lson(p))&&lft(rson(p))?dis(rit(lson(p)),lft(rson(p))):0);
lft(p)=lft(lft(lson(p))?lson(p):rson(p));rit(p)=rit(rit(rson(p))?rson(p):lson(p));
}
void add(int x,int v,int p,int tl=1,int tr=n){
if(tl==tr)return cnt(p)+=v,lft(p)=rit(p)=cnt(p)?tl:0,void();
int mid=tl+tr>>1;
if(x<=mid){
if(!lson(p))lson(p)=nwnd();
add(x,v,lson(p),tl,mid);
}
else{
if(!rson(p))rson(p)=nwnd();
add(x,v,rson(p),mid+1,tr);
}
sprup(p);
}
int mrg(int p,int q,int tl=1,int tr=n){
if(!p||!q)return p|q;
if(tl==tr)return cnt(p)+=cnt(q),lft(p)=rit(p)=cnt(p)?tl:0,p;
int mid=tl+tr>>1;
lson(p)=mrg(lson(p),lson(q),tl,mid),rson(p)=mrg(rson(p),rson(q),mid+1,tr);
return sprup(p),p;
}
}segt;
long long ans;
void dfs0(int x=1){
for(int i=0;i<add[x].size();i++)segt.add(dfn[add[x][i]],1,segt.root[x]);
for(int i=0;i<del[x].size();i++)segt.add(dfn[del[x][i]],-1,segt.root[x]);
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(y==fa[x])continue;
dfs0(y);
segt.root[x]=segt.mrg(segt.root[x],segt.root[y]);
}
int p=segt.root[x];
ans+=(segt.sum(p)+(segt.lft(p)?dis(segt.lft(p),segt.rit(p)):0))/2;
}
int main(){
cin>>n>>m;
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
nei[x].pb(y),nei[y].pb(x);
}
dep[1]=1;dfs();
st.init();
while(m--){
int x,y;
scanf("%d%d",&x,&y);
int a=lca(x,y);
add[x].pb(x),add[x].pb(y);
add[y].pb(x),add[y].pb(y);
del[a].pb(x),del[a].pb(y);
del[fa[a]].pb(x),del[fa[a]].pb(y);
}
segt.init();
dfs0();
cout<<ans/2;
return 0;
}
原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-p5327.html
- mongodb数据结构与基本操作增删改查整理(二)
- 使用在线重定义重构亿级分区表(r10笔记第34天)
- 【Go 语言社区】一个WebSocket的简单Echo例子
- Java基础-day10-代码题-继承&抽象类
- 闪回区空间不足引发的SQL问题分析(r10笔记第32天)
- JavaScript Window - 浏览器对象模型
- 纯CSS实现的圆角折叠菜单特效代码
- MySQL和Oracle中的半连接测试总结(一)(r10笔记第31天)
- 【Go 语言社区】关于select和channel数组的配合使用--转
- Java基础-day09-代码题-对象;类;封装
- MySQL replace into的使用细则(r10笔记第48天)
- Win10下用Anaconda安装TensorFlow
- 【Go 语言社区】跨域问题解决方案:jsonP客户端和服务器代码
- 图;代码轻松理解,代理
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 独家 | 如何用XGBoost做时间序列预测?
- 关于Web点击劫持的一些实例
- LeetCode 爬取官网所有题目和自己的最近题解
- 一张图实现3D人脸建模!这是中科院博士生入选ECCV的新研究 | 开源
- client-go 之 DeltaFIFO 实现原理
- KEDA-Kubernetes 中基于事件驱动的自动伸缩
- 更新 Kubernetes APIServer 证书
- 0810-5.15.1-Impala执行invalidate metadata异常分析
- 笔记日记debug,推荐这个插件里的模板
- 我用 Java 8 写了一段逻辑,同事直呼看不懂,你试试看。。
- sklearn 模型的保存与加载
- R:如何使用RMarkdown渲染中文pdf报告
- 图解 SQL,这也太形象了吧!
- 用python爬取前程无忧网,看看我们是否真的“前程无忧”?
- 超硬核的 Python 数据可视化教程!