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. python实现地理编码

    python实现地理编码 去高德地图申请好key python代码 # -*- coding:utf_8 -*- # !/usr/bin/python37 """ @au ...

  2. 初学Python-day1 运算符和数据类型

  3. 华为在HDC2021发布全新HMS Core 6 宣布跨OS能力开放

    [2021年10月22日·东莞]华为开发者大会 2021(Together)于今天正式开幕,华为在主题演讲中正式发布全新的HMS Core 6,向全球开发者开放7大领域的69个Kit和21,738个A ...

  4. 这12种场景Spring事务会失效!

    前言 对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了.在某些业务场景下,如果一个请求中,需要同时写入多张表的数据.为了保证操作的原子性 (要么同时成功,要么同时失败),避免数据 ...

  5. android tcp通讯

    Andoird TCP通讯 前言 最近在写一个即时通讯的项目,有一些心得,写出来给大家分享指正一下. 简单描述一下这个项目: 实时查询车辆运行状态的项目,走TCP通迅. 接口采用GZIP压缩. 后台是 ...

  6. 嵌入式单片机stm32之DMA实验

    一. 对于大容量的STM32芯片有2个DMA控制器,控制器1有7个通道,控制器2有5个通道 每个通道都可以配置一些外设的地址. 二. 通道的配置过程: 1. 首先设置CPARx寄存器和CMARx寄存器 ...

  7. 最近公共祖先 牛客网 程序员面试金典 C++ Python

    最近公共祖先 牛客网 程序员面试金典 C++ Python 题目描述 有一棵无穷大的满二叉树,其结点按根结点一层一层地从左往右依次编号,根结点编号为1.现在有两个结点a,b.请设计一个算法,求出a和b ...

  8. hdu 1227 Fast Food(DP)

    题意: X轴上有N个餐馆.位置分别是D[1]...D[N]. 有K个食物储存点.每一个食物储存点必须和某个餐厅是同一个位置. 计算SUM(Di-(离第i个餐厅最近的储存点位置))的最小值. 1 < ...

  9. Android WebView 实现文件选择、拍照、录制视频、录音

    原文地址:Android WebView 实现文件选择.拍照.录制视频.录音 | Stars-One的杂货小窝 Android中的WebView如果不进行相应的设置,H5页面的上传按钮是无法触发And ...

  10. x64 InlineHook 黑魔法

    目录 x64 InlineHook 黑魔法 为什么不能用X86 的HOOK方式? 原理:jmp + rip 进行寻址6字节方式跳转 手动InlineHook 临时地址x(找一块空内存) 计算偏移 源地 ...