CF1307F Cow and Vacation
一、题目
二、解法
这道题又是我自己想出来的,但是好像 \(3300\) 的评分有点虚高了吧。
其实本题就是问的一个可达性,那么我们可以考虑往连通性上思考。首先考虑本题是否是双向联通的,也就是从 \(a\) 出发能到达 \(b\),那么从 \(b\) 出发就能到达 \(a\),这个性质不难证明。
但是考察连通性有一个问题,也就是所谓“连通”的两点能到达的范围是不同的(因为起始步数的原因),如果我们想要利用连通的性质需要连通块上每个点是等价的(无论谁是出发点都可互达),然后我们发现如果只考虑关键点就正好契合这一点,因为只要到了关键点剩余步数就会重置为 \(k\)
那么我们考虑建出关键点的连通网络,那么查询就可以考虑从一个距离起点小于等于 \(m\) 的关键点上车,然后从一个距离终点小于等于 \(m\) 的关键点下车;或者是不用关键点直接走。
思路就是上面那些了,现在来说一下实现细节(这里参考了一下其他题解):
连通网络的建立可以考虑用 \(\tt bfs\) 和并查集,我们从每个关键点开始走 \(\frac{m}{2}\) 步,然后把这些范围内的点并查集合并。然后如果 \(m\) 是奇数就很不好判断,所以我们把边拆成点,那么距离都\(\times 2\),我们走 \(m\) 步即可。
对于询问我们可以从起点和终点都往 \(\tt lca\) 跳 \(m\) 步,然后看是否在一个并查集中,不难证明往上跳满是最优的(考虑我们 \(\tt bfs\) 的过程),所以总时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 400005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,q,p[M],d[M],dep[M],fa[M][20];
vector<int> g[M];
void dfs(int u,int p)
{
fa[u][0]=p;
dep[u]=dep[p]+1;
for(int i=1;i<20;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto v:g[u]) if(v^p) dfs(v,u);
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
if(u==v) return u;
for(int i=19;i>=0;i--)
if(fa[u][i]^fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int jump(int x,int y)
{
for(int i=19;i>=0;i--)
if(y&(1<<i)) x=fa[x][i];
return x;
}
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void bfs()
{
queue<int> q;
for(int i=1;i<2*n;i++)
p[i]=i,d[i]=-1;
for(int i=1;i<=k;i++)
{
int u=read();
d[u]=0;q.push(u);
}
while(!q.empty())
{
int u=q.front();q.pop();
if(d[u]==m) break;
for(int v:g[u])
{
p[find(v)]=find(u);
if(d[v]==-1)
{
d[v]=d[u]+1;
q.push(v);
}
}
}
}
signed main()
{
n=read();m=read();k=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
g[u].push_back(n+i);g[n+i].push_back(u);
g[v].push_back(n+i);g[n+i].push_back(v);
}
dfs(1,0);bfs();
q=read();
while(q--)
{
int u=read(),v=read(),x=lca(u,v);
int d=dep[u]+dep[v]-2*dep[x];
if(d<=2*m) {puts("YES");continue;}
int A=dep[u]-dep[x]>=m?find(jump(u,m)):find(jump(v,d-m));
int B=dep[v]-dep[x]>=m?find(jump(v,m)):find(jump(u,d-m));
if(A==B) puts("YES");
else puts("NO");
}
}