LOJ 题面传送门

人傻常数大,需要狠命卡……/wq/wq

画个图可以发现两条路径相交无非以下两种情况(其中红色部分为两路径的重叠部分,粉色、绿色的部分分别表示两条路径):

考虑如何计算它们的贡献,对于第一种情况,我们枚举两条路径 LCA 中深度较大者,也就是上图中的 A 点。枚举这个点 \(x\) 以后进而枚举对应的路径,设其为 \(u\to x\to v\),那么我们考虑二分重叠部分的长度 \(len\),那么这个重叠部分要么是 \(u\) 的 \(dep_u-dep_x-len\) 级祖先 \(fu\) 到 \(x\) 的路径,要么是 \(v\) 的 \(dep_v-dep_x-len\) 级祖先 \(fv\) 到 \(x\) 的路径,因此我们需要检验是否存在一条路径经过 \(fu\to x\) 上所有点或者经过 \(fv\to x\) 上所有点。我们考虑用线段树,维护一个端点在 \(x\) 子树内,一个端点不在 \(x\) 子树内的路径。具体来说我们求出每个点的 DFS 序 \(dfn_x\),对于线段树上下标为 \(p\) 的位置,我们存有多少条路径 \((u,v)\),满足 \(dfn_u=x\),且 \(u\) 在 \(x\) 子树内,\(v\) 在 \(x\) 子树外,这样检验时只需做一遍区间求和。那么怎么维护这个线段树呢?我们考虑对于每条路径,在 \(u\) 处的线段树上下标为 \(dfn_u\) 的位置上加一,\(v\) 处的线段树上下标为 \(dfn_v\) 的位置上加一,\(\text{LCA}(u,v)\) 处线段树上 \(dfn_u,dfn_v\) 位置上各减一,然后线段树合并即可。

对于第二种情况,我们考虑在两条路径交界处(也就是上图中粉色、绿色线段的上端点,红色线段的下段点)处统计贡献,我们还是对整棵树进行一遍 DFS 并枚举对应的点 \(x\),那么可以发现,如果我们把一个端点在 \(x\) 子树内,一个端点在子树外的路径拎出来并按照它们在子树外的端点的 DFS 序从小到大排序,最优的一对路径在排好序的序列中肯定是相邻的,因此我们考虑将所有路径压入一个 set,每次加入一棵子树时启发式合并,将大小小的 set 中元素暴力插入大小大的 set 中,插入时在 setlower_bound 中找到 DFS 序与其相邻的点更新答案即可。

时间复杂度 \(n\log^2n\)。

const int MAXN=2e5;
const int LOG_N=19;
int n,k,hd[MAXN+5],nxt[MAXN+5],to[MAXN+5],ec=0,cc[MAXN+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dfn[MAXN+5],tim,edt[MAXN+5];
int dep[MAXN+5],top[MAXN+5],siz[MAXN+5],wson[MAXN+5];
int fa[LOG_N+2][MAXN+5];
void dfs01(int x){
siz[x]=1;dfn[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];dep[y]=dep[x]+1;dfs01(y);siz[x]+=siz[y];
if(siz[y]>siz[wson[x]]) wson[x]=y;
} edt[x]=tim;
}
void dfs02(int x,int tp){
top[x]=tp;if(wson[x]) dfs02(wson[x],tp);
for(int e=hd[x];e;e=nxt[e]) if(to[e]^wson[x]) dfs02(to[e],to[e]);
}
int getlca(int x,int y){
while(top[x]^top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[0][top[x]];
} return (dep[x]<dep[y])?x:y;
}
struct dat{int a,b,lc,dis;} c[MAXN+5];
int get_kanc(int x,int k){
for(int i=LOG_N;~i;i--) if(k>>i&1) x=fa[i][x];
return x;
}
namespace segtree{
const int MAXP=MAXN*40;
struct node{int ch[2],val;} s[MAXP+5];
int rt[MAXN+5],ncnt=0;
void pushup(int k){s[k].val=s[s[k].ch[0]].val+s[s[k].ch[1]].val;}
void insert(int &k,int l,int r,int p,int v){
if(!k) k=++ncnt;if(l==r) return s[k].val+=v,void();
int mid=l+r>>1;
if(p<=mid) insert(s[k].ch[0],l,mid,p,v);
else insert(s[k].ch[1],mid+1,r,p,v);
pushup(k);
}
int query(int k,int l,int r,int ql,int qr){
if(!k) return 0;
if(ql<=l&&r<=qr) return s[k].val;
int mid=l+r>>1;
if(qr<=mid) return query(s[k].ch[0],l,mid,ql,qr);
else if(ql>mid) return query(s[k].ch[1],mid+1,r,ql,qr);
else return query(s[k].ch[0],l,mid,ql,mid)+query(s[k].ch[1],mid+1,r,mid+1,qr);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;int z=++ncnt,mid=l+r>>1;
if(l==r) return s[z].val=s[x].val+s[y].val,z;
s[z].ch[0]=merge(s[x].ch[0],s[y].ch[0],l,mid);
s[z].ch[1]=merge(s[x].ch[1],s[y].ch[1],mid+1,r);
pushup(z);return z;
}
}
using segtree::rt;
using segtree::insert;
using segtree::query;
using segtree::merge;
vector<int> pth[MAXN+5];
vector<pii> add[MAXN+5],del[MAXN+5];
pair<int,int> mx_id1=mp(0,1);
pair<int,pii> mx_id2;
void dfs1(int x){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];dfs1(y);
rt[x]=merge(rt[x],rt[y],1,n);
}
insert(rt[x],1,n,dfn[x],cc[x]);
for(int id:pth[x]){
insert(rt[x],1,n,dfn[c[id].a],-1);
insert(rt[x],1,n,dfn[c[id].b],-1);
if(c[id].dis<mx_id1.fi) continue;
if(c[id].a!=x){
int len=dep[c[id].a]-dep[x];
int l=1,r=dep[c[id].a]-dep[x],p=0;
while(l<=r){
int mid=l+r>>1,anc=get_kanc(c[id].a,len-mid);
if(query(rt[x],1,n,dfn[anc],edt[anc])) p=mid,l=mid+1;
else r=mid-1;
} chkmax(mx_id1,mp(p,id));
} if(c[id].b!=x){
int len=dep[c[id].b]-dep[x];
int l=1,r=dep[c[id].b]-dep[x],p=0;
while(l<=r){
int mid=l+r>>1,anc=get_kanc(c[id].b,len-mid);
if(query(rt[x],1,n,dfn[anc],edt[anc])) p=mid,l=mid+1;
else r=mid-1;
} chkmax(mx_id1,mp(p,id));
}
}
}
bool has_anc(int x,int y){//whether x and y are ancestor and son
return (getlca(x,y)==x)||(getlca(x,y)==y);
}
int calc_fa_son(int x1,int y1,int x2,int y2){//y1 is the ancestor of x1
int lc=getlca(x1,x2);
return max(dep[lc]-max(dep[y1],dep[y2]),0);
}
int calc(int x,int y){
if(x==y) return -1;
if(c[x].dis<mx_id2.fi) return 0;
if(c[y].dis<mx_id2.fi) return 0;
if(!has_anc(c[x].lc,c[y].lc)) return 0;
return calc_fa_son(c[x].a,c[x].lc,c[y].a,c[y].lc)+
calc_fa_son(c[x].b,c[x].lc,c[y].a,c[y].lc)+
calc_fa_son(c[x].a,c[x].lc,c[y].b,c[y].lc)+
calc_fa_son(c[x].b,c[x].lc,c[y].b,c[y].lc);
}
set<pii> stv[MAXN+5];
bool ban[MAXN+5];
void upd_ans(int x,int y){chkmax(mx_id2,mp(calc(x,y),mp(x,y)));}
void ins_st(set<pii> &st,pii p){
if(c[p.se].dis<mx_id2.fi) return ban[p.se]=1,void();
else{
set<pii>::iterator it=st.upper_bound(p);
if(it!=st.end()) upd_ans(p.se,it->se);
if(it!=st.begin()) upd_ans(p.se,(--it)->se);
st.insert(p);
}
}
void dfs2(int x){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];dfs2(y);
if(stv[y].size()>stv[x].size()) stv[x].swap(stv[y]);
for(pii p:stv[y]) ins_st(stv[x],p);
}
for(pii p:add[x]) ins_st(stv[x],mp(dfn[p.fi],p.se));
for(pii p:del[x]) if(!ban[p.se])
stv[x].erase(stv[x].find(mp(dfn[p.fi],p.se)));
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&n,&k);
for(int i=2,f;i<=n;i++) scanf("%d",&f),adde(f,i),fa[0][i]=f;
dfs01(1);dfs02(1,1);
for(int i=1;i<=LOG_N;i++) for(int j=1;j<=n;j++) fa[i][j]=fa[i-1][fa[i-1][j]];
for(int i=1;i<=k;i++){
scanf("%d%d",&c[i].a,&c[i].b);
c[i].lc=getlca(c[i].a,c[i].b);
if(c[i].lc!=c[i].a&&c[i].lc!=c[i].b) pth[c[i].lc].pb(i);
cc[c[i].a]++;cc[c[i].b]++;
add[c[i].a].pb(mp(c[i].b,i));
add[c[i].b].pb(mp(c[i].a,i));
del[c[i].lc].pb(mp(c[i].a,i));
del[c[i].lc].pb(mp(c[i].b,i));
c[i].dis=dep[c[i].a]+dep[c[i].b]-(dep[c[i].lc]<<1);
} dfs1(1);
for(int i=1;i<=k;i++) if(i^mx_id1.se)
chkmax(mx_id2,mp(calc(i,mx_id1.se),mp(i,mx_id1.se)));
dfs2(1);
printf("%d\n%d %d\n",mx_id2.fi,mx_id2.se.fi,mx_id2.se.se);
return 0;
}
/*
7 2
1 1 2 2 3 3
4 7
5 6
*/

LOJ 3066 - 「ROI 2016 Day2」快递(线段树合并+set 启发式合并)的更多相关文章

  1. LOJ#2343. 「JOI 2016 Final」集邮比赛 2

    题目地址 https://loj.ac/problem/2343 题解 首先处理出\(f[i]\)表示以当前位置开头(J,O,I)的合法方案数.这个显然可以\(O(n)\)处理出来.然后考虑在每个位置 ...

  2. [LOJ#2878]. 「JOISC 2014 Day2」邮戳拉力赛[括号序列dp]

    题意 题目链接 分析 如果走到了下行车站就一定会在前面的某个车站走回上行车站,可以看成是一对括号. 我们要求的就是 类似 代价最小的括号序列匹配问题,定义 f(i,j) 表示到 i 有 j 个左括号没 ...

  3. loj #6032. 「雅礼集训 2017 Day2」水箱 线段树优化DP转移

    $ \color{#0066ff}{ 题目描述 }$ 给出一个长度为 \(n\) 宽度为 \(1\) ,高度无限的水箱,有 \(n-1\) 个挡板将其分为 \(n\) 个 \(1 - 1\) 的小格, ...

  4. LOJ #2877. 「JOISC 2014 Day2」交朋友 并查集+BFS

    这种图论问题都挺考验小思维的. 首先,我们把从 $x$ 连出去两条边的都合并了. 然后再去合并从 $x$ 连出去一条原有边与一条新边的情况. 第一种情况直接枚举就行,第二种情况来一个多源 bfs 即可 ...

  5. LOJ #2876. 「JOISC 2014 Day2」水壶 BFS+最小生成树+倍增LCA

    非常好的一道图论问题. 显然,我们要求城市间的最小生成树,然后查询路径最大值. 然后我们有一个非常神的处理方法:进行多源 BFS,处理出每一个城市的管辖范围. 显然,如果两个城市的管辖范围没有交集的话 ...

  6. LOJ.121.[离线可过]动态图连通性(线段树分治 按秩合并)

    题目链接 以时间为下标建线段树.线段树每个节点开个vector. 对每条边在其出现时间内加入线段树,即,把这条边按时间放在线段树的对应区间上,会影响\(O(\log n)\)个节点. 询问就放在线段树 ...

  7. 【LOJ】#3034. 「JOISC 2019 Day2」两道料理

    LOJ#3034. 「JOISC 2019 Day2」两道料理 找出最大的\(y_{i}\)使得\(sumA_{i} + sumB_{y_i} \leq S_{i}\) 和最大的\(x_{j}\)使得 ...

  8. 【LOJ】#3033. 「JOISC 2019 Day2」两个天线

    LOJ#3033. 「JOISC 2019 Day2」两个天线 用后面的天线更新前面的天线,线段树上存历史版本的最大值 也就是线段树需要维护历史版本的最大值,后面的天线的标记中最大的那个和最小的那个, ...

  9. Loj #2731 「JOISC 2016 Day 1」棋盘游戏

    Loj 2731 「JOISC 2016 Day 1」棋盘游戏 JOI 君有一个棋盘,棋盘上有 \(N\) 行 \(3\) 列 的格子.JOI 君有若干棋子,并想用它们来玩一个游戏.初始状态棋盘上至少 ...

随机推荐

  1. netty系列之:使用netty实现支持http2的服务器

    目录 简介 基本流程 CleartextHttp2ServerUpgradeHandler Http2ConnectionHandler 总结 简介 上一篇文章中,我们提到了如何在netty中配置TL ...

  2. 【UE4】GAMES101 图形学作业3:Blinn-Phong 模型与着色

    总览 在这次编程任务中,我们会进一步模拟现代图形技术.我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与Fragment Shader,并且支持了纹理映 ...

  3. 【UE4】GAMES101 图形学作业1:mvp 模型、视图、投影变换

    总览 到目前为止,我们已经学习了如何使用矩阵变换来排列二维或三维空间中的对象.所以现在是时候通过实现一些简单的变换矩阵来获得一些实际经验了.在接下来的三次作业中,我们将要求你去模拟一个基于CPU 的光 ...

  4. 【UE4 C++ 基础知识】<7> 容器——TSet

    概述 TSet是一种快速容器类,(通常)用于在排序不重要的情况下存储唯一元素. TSet 类似于 TMap 和 TMultiMap,但有一个重要区别:TSet 是通过对元素求值的可覆盖函数,使用数据值 ...

  5. 【c++ Prime 学习笔记】目录索引

    第1章 开始 第Ⅰ部分 C++基础 第2章 变量和基本类型 第3章 字符串.向量和数组 第4章 表达式 第5章 语句 第6章 函数 第7章 类 第 Ⅱ 部分 C++标准库 第8章 IO库 第9章 顺序 ...

  6. BUAA_2020_软件工程_软件案例分析作业

    项目 内容 这个作业属于那个课程 班级博客 这个作业的要求在哪里 作业要求 我在这个课程的目标是 学习掌握软件工程的相关知识 这个作业在哪个具体方面帮我实现目标 通过对具体软件案例的分析学习软件工程 ...

  7. Python课程笔记(四)

    1.模块的导入 相当于Java的包或C语言的头文件 (1) import math s = math.sqrt(25) print(s) (2) from math import sqrt s=mat ...

  8. sql 多表联合查询更新

    sqlserver: update A a set a.i = b.k from B b where a.key = b.key oracle : update A a set a.i = (sele ...

  9. (四)FastDFS 高可用集群架构学习---后期运维--基础知识及常用命令

    1.fastdfs 七种状态 FDFS_STORAGE_STATUS:INIT :初始化,尚未得到同步已有数据的源服务器 FDFS_STORAGE_STATUS:WAIT_SYNC :等待同步,已得到 ...

  10. LOTO虚拟示波器软件功能演示之——FIR数字滤波

    本文章介绍一下LOTO示波器新出的功能--FIR数字滤波的功能. 在此之前我们先来了解一下带通滤波和带阻滤波.我们都知道每个信号是不同频率不同幅值正弦波的线性叠加,为了方便直接得观察到这种现象,就有了 ...