CF983E NN country
题意
给定一棵树和若干条路线,每条路线相当于 $a,b$ 之间的路径,途径路径上的每个点。
给出若干个询问,每次询问从 $x$ 到 $y$ 至少需要利用几条路线。
Solution
考虑子问题,两个询问点在同一条链上,这样问题就类似 [SCOI2015] 国旗计划,只不过这道题是环上问题,但思路相同。
对于链,考虑贪心,对于每一个点 $fr_i$ 跳到能到达的最远的点 $to_i$,容易想到下一步应当是跳到 $to_{to_i}$,故考虑倍增优化这个不断往前跳的过程。定义 $jump_{i,k}$ 为点 $i$ 跳 $2^k$ 步能到达的最远节点,可以用 $\mathcal{O}(n\log n)$ 复杂度的时间来处理出 $jump$ 数组。
考虑树上的两个点,对于 $x$ 是 $y$ 的祖先节点($y$ 为 $x$ 祖先节点时同理)的情况,同链上情况处理。
对于两个点分别不是对方父亲节点的情况,考虑将问题拆分为 $x$ 到 $lca$ 和 $y$ 到 $lca$ 两个问题处理。令 $ans_x$ 为 $x$ 跳到 $lca$ 的最小步数,$ans_y$ 为 $y$ 跳到 $lca$ 的最小步数,$pre_x$ 为 $x$ 向上跳 $ans_x-1$ 步到达的深度最浅的节点,即跳到 $lca$ 的前一个节点,$pre_y$ 同理。考虑两种情况:
- 有一条路线同时经过 $pre_x$ 和 $pre_y$;
- 不存在一条路线同时经过 $pre_x$ 和 $pre_y$。
对于第二种情况,答案即为 $ans_x+ans_y$,对于第一种情况,答案为 $ans_x+ans_y-1$。问题转化为如何维护是否存在一条路线经过两个点。
发现对于一个节点 $u$,只要 $u$ 的子树中存在一个点,使得存在一条从其出发的路径在 $v$ 的子树中结束,则存在一条路径同时经过 $u$ 和 $v$。考虑通过 dfs 序转化为区间问题,令 $size_i$ 为节点 $i$ 的子树大小,则问题进一步转化为询问是否存在一条路径 $(fr,to)$ 使得 $dfn_{fr}\in[dfn_x,dfn_x+size_x-1]$ ,$dfn_{to}\in[dfn_y,dfn_y+size_y-1]$。考虑二维数点,即查询平面上矩形 $[(dfn_x,dfn_y),(dfn_x+size_x-1,dfn_y+size_y-1)]$ 是否有点。将询问离线排序并用树状数组维护即可。
有个小细节:由于 $(fr,to)$ 和 $(to,fr)$ 在此题中是等价的,故在插入点时都应插入,否则可能会统计不到这个点。
其他具体实现细节见代码,以及由于我不会倍增求 $lca$,所以写了个树剖。
总时间复杂度应该是 $\mathcal{O}(n\log n)$ 的,但是有巨大常数,可以通过本题。
code
#include<bits/stdc++.h>
inline int read()
{
int res=0,flag=1;
char ch=getchar();
while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
return res*flag;
}
struct edge
{
int to,nxt;
};
int n,m,q,tot=0;
int head[200010];
struct edge ed[400010];
void add_edge(int fr,int to)
{
ed[++tot]=(edge){to,head[fr]};
head[fr]=tot;
return ;
}
namespace TreePartition
{
struct node
{
int fa,son;
int top;
int size;
int dep;
};
const int root=1;
struct node nd[200010];
void set(int fa,int fr)
{
nd[fr].size=1;
nd[fr].fa=fa;
nd[fr].dep=nd[fa].dep+1;
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(to==fa)
continue;
set(fr,to);
nd[fr].size+=nd[to].size;
if(nd[to].size>nd[nd[fr].son].size)
nd[fr].son=to;
}
return ;
}
void dfs(int fa,int fr)
{
if(nd[fr].son!=0)
{
int to=nd[fr].son;
nd[to].top=nd[fr].top;
dfs(fr,to);
}
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(to==fa||to==nd[fr].son)
continue;
nd[to].top=to;
dfs(fr,to);
}
return ;
}
void init()
{
nd[root].dep=1;
nd[root].top=root;
set(0,root);
dfs(0,root);
return ;
}
int lca(int x,int y)
{
int fx=nd[x].top;
int fy=nd[y].top;
while(fx!=fy)
{
if(nd[fx].dep<nd[fy].dep)
std::swap(x,y),std::swap(fx,fy);
if(x==root)
return root;
x=nd[fx].fa;
fx=nd[nd[fx].fa].top;
}
if(nd[x].dep>nd[y].dep)
std::swap(x,y);
return x;
}
}
namespace BIT
{
struct point
{
int x,y;
bool operator <(const struct point &other)const
{
if(this->x==other.x)
return this->y<other.y;
return this->x<other.x;
}
};
struct question
{
int id;
int val;
int x,y;
bool operator <(const struct question &other)const
{
if(this->x==other.x)
return this->y<other.y;
return this->x<other.x;
}
};
int pts,cnt;
struct point points[800010];
struct question questions[1600010];
int data[400010];
int lowbit(int x)
{
return x&(-x);
}
void modify(int pos,int val)
{
if(pos==0)
return ;
for(int i=pos;i<=n;i+=lowbit(i))
data[i]+=val;
return ;
}
int query(int pos)
{
int res=0;
for(int i=pos;i>=1;i-=lowbit(i))
res+=data[i];
return res;
}
};
namespace solve
{
int dfncnt;
int ans[200010],tmp[200010];
int dfn[200010];
int jump[200010][21];
std::vector<int> graph[200010];
void dfs(int fa,int fr)
{
dfn[fr]=++dfncnt;
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(to==fa)
continue;
dfs(fr,to);
}
return ;
}
void set(int fa,int fr)
{
using namespace TreePartition;
jump[fr][0]=fr;
for(auto Lca:graph[fr])
if(nd[jump[fr][0]].dep>nd[Lca].dep)
jump[fr][0]=Lca;
for(int i=head[fr];i!=0;i=ed[i].nxt)
{
int to=ed[i].to;
if(to==fa)
continue;
set(fr,to);
if(nd[jump[fr][0]].dep>nd[jump[to][0]].dep)
jump[fr][0]=jump[to][0];
}
return ;
}
void input()
{
n=read();
for(int to=2;to<=n;to++)
{
int fr=read();
add_edge(fr,to);
add_edge(to,fr);
}
TreePartition::init();
dfs(0,1);
m=read();
for(int i=1;i<=m;i++)
{
int fr=read(),to=read();
int Lca=TreePartition::lca(fr,to);
if(fr!=Lca)
graph[fr].push_back(Lca);
if(to!=Lca)
graph[to].push_back(Lca);
using namespace BIT;
points[++pts]=(point){dfn[fr],dfn[to]};
points[++pts]=(point){dfn[to],dfn[fr]};
}
set(0,1);
q=read();
return ;
}
void init()
{
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)
jump[j][i]=jump[jump[j][i-1]][i-1];
return ;
}
void solve()
{
for(int i=1;i<=q;i++)
{
using namespace TreePartition;
int fr=read(),to=read();
int Lca=lca(fr,to);
if(dfn[fr]>dfn[to])
std::swap(fr,to);
for(int j=20;j>=0;j--)
if(jump[fr][j]!=0&&nd[jump[fr][j]].dep>nd[Lca].dep)
fr=jump[fr][j],ans[i]+=(1<<j);
for(int j=20;j>=0;j--)
if(jump[to][j]!=0&&nd[jump[to][j]].dep>nd[Lca].dep)
to=jump[to][j],ans[i]+=(1<<j);
if(nd[jump[fr][0]].dep>nd[Lca].dep||nd[jump[to][0]].dep>nd[Lca].dep)
{
ans[i]=-1;
continue;
}
if(fr==Lca)
{
ans[i]++;
continue;
};
ans[i]+=2;
int sizefr=nd[fr].size;
int sizeto=nd[to].size;
using namespace BIT;
questions[++cnt]=(question){i,1,dfn[fr]+sizefr-1,dfn[to]+sizeto-1};
questions[++cnt]=(question){i,-1,dfn[fr]+sizefr-1,dfn[to]-1};
questions[++cnt]=(question){i,-1,dfn[fr]-1,dfn[to]+sizeto-1};
questions[++cnt]=(question){i,1,dfn[fr]-1,dfn[to]-1};
}
using namespace BIT;
std::sort(points+1,points+pts+1);
std::sort(questions+1,questions+cnt+1);
for(int i=1,now=1;i<=cnt;i++)
{
while(now<=pts&&points[now].x<=questions[i].x)
modify(points[now].y,1),now++;
tmp[questions[i].id]+=questions[i].val*query(questions[i].y);
}
for(int i=1;i<=q;i++)
{
if(tmp[i]>0)
ans[i]--;
printf("%d\n",ans[i]);
}
return ;
}
};
int main(int argc,const char *argv[])
{
solve::input();
solve::init();
solve::solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)