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. 微信h5跳转小程序wx-open-launch-weapp开放标签不显示(已解决)

    前言: 前几天成功对接了跳转第三方小程序的功能,今天有个页面有需要对接.但是奇怪的是用的和上次一模一样的配置,但就是死活不显示wx-open-launch-weapp这个开放标签的按钮,看不到任何效果 ...

  2. 2021.9.26考试总结[NOIP模拟62]

    T1 set 从\(0\)到\(n\)前缀余数有\(n+1\)个,但只有\(n\)种取值,找到一样的两个输出区间即可. \(code:\) T1 #include<bits/stdc++.h&g ...

  3. Spring Security:简单的保护一个SpringBoot应用程序(总结)

    Spring Security 在 Java类中的配置 在 Spring Security 中使用 Java配置,可以轻松配置 Spring Security 而无需使用 XML . 在Spring ...

  4. Envoy实现.NET架构的网关(三)代理GRPC

    什么是GRPC gRPC是一种与语言无关的高性能远程过程调用 (RPC) 框架.gRPC 的主要好处是: 现代.高性能.轻量级的 RPC 框架. 契约优先的 API 开发,默认使用协议缓冲区,与语言无 ...

  5. 从零开始的DIY智能浇水应用

    前言 作为一个新世纪打工人,平常也会去养一些花草,来给我的房间增加点绿色和活力,但是常常因为工作忙而忘记一些事情.,毕竟我大部分的时间都是陪伴着电脑的(严正声明:我不是个单身狗!!! (¬◡¬)✧), ...

  6. git commit--fatal: unable to auto-detect email address

    git commit的时候报错 *** Please tell me who you are. Run git config --global user.email "you@example ...

  7. Python 语法错误 except Exception, e: ^ SyntaxError: invalid syntax

    出这个问题是因为python2和python3 语法有些不同 python2 和 3 处理 except 子句的语法有点不同,需要注意: Python2 try: print ("hello ...

  8. Bzoj通过5题纪念

    我A了五题啦!!!

  9. pycharm软件安装和破解

    pycharm安装 1. 进入pycharm的官网 --- 下载专业版的pycharm 2. 双击下载好的软件,下一步 3. 选择需要安装软件的路径 --- 注意: 尽量不要将软件装在C盘里 4. 默 ...

  10. 精心整理Java微服务最全面试题集(含答案)

    微服务架构相关 大型网站架构演变过程 网站架构演变演变过程 传统架构 → 分布式架构 → SOA架构 → 微服务架构 什么是分布式架构 分布式架构就是将传统结构按照模块进行拆分,不同的人负责不同的模块 ...