CF2132F 解题报告
CF2132F 解题报告
简要题意
给出 \(n\) 个点 \(m\) 条边的无向连通图,询问 \(q\) 次。每次询问需要回答对于一个点 \(x\),在“从 \(1\) 到 \(n\) 必须经过的边”中和 \(x\) 距离最小的边的编号(如果有多个,取编号最小)。
数据范围:保证所有输入数字均为不超过 \(2 \times 10^5\) 的正整数。
分析
“必须经过的边”就是指割边。
那么题目转化为求 \(1\) 到 \(n\) 所有路径构成的图上的割边。
我们考虑先找到这张图上的点,这可以用两次 dfs 实现。
具体地,在这个图上的点一定可以在从 \(n\) 通过 dfs 访问到 \(1\) 的过程中被访问到。但是只 dfs 一次会有一种情况:在这张图上的一个环中有一个点 \(u\),访问到了它在 dfs 生成树上的父亲 \(v\)。但是此时还没有回溯到 \(v\),因此 \(v\) 是没有被打标记的,所以 \(u\) 打不上不标记。因此我们需要再 dfs 一次,来确保所有点都被打上了标记。
然后就是正常求割边。最后就 bfs 求出每一个点的答案即可。
代码
const int N=2e5+100;
int T,n,m,k,cnt;
struct Edge{int from,to,id;}e[N<<1];
int num,h[N];
void add(int f,int t,int d){e[++num].from=h[f],e[num].to=t,e[num].id=d,h[f]=num;}
int dfn[N],low[N],vis[N],dfu;
bool able[N],vis1[N];
bool dfs(int u,int fa){
vis1[u]=true;
if(u==1) return able[u]=true;
for(int i=h[u];i;i=e[i].from){
int v=e[i].to;
if(v==fa) continue;
if(vis1[v]){able[u]|=able[v];continue;}
able[u]|=dfs(v,u);
}
return able[u];
}
void tarjan(int u,int fa){
low[u]=dfn[u]=++dfu;
for(int i=h[u];i;i=e[i].from){
int v=e[i].to;
if(v==fa || !able[v]) continue;
if(!dfn[v]){
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
ckmn(vis[v],e[i].id),ckmn(vis[u],e[i].id);
}
else
low[u]=min(low[u],dfn[v]);
}
}
pii lst[N];
queue<pii> q;
void solve(){
n=read(),m=read();
For(i,1,n) h[i]=dfn[i]=low[i]=able[i]=vis1[i]=0,vis[i]=inf;
num=dfu=cnt=0;
int u,v;
For(i,1,m) u=read(),v=read(),add(u,v,i),add(v,u,i);
dfs(n,0);
For(i,1,n) vis1[i]=0;
dfs(n,0);
tarjan(1,0);
//For(i,1,n) printf("%d ",vis[i]);
//putchar('\n');
For(i,1,n)
if(vis[i]!=inf)
lst[++cnt]=make_pair(vis[i],i);
sort(lst+1,lst+cnt+1,less<pii>());
For(i,1,cnt) q.push(lst[i]);
while(!q.empty()){
int p=q.front().first,u=q.front().second;q.pop();
for(int i=h[u];i;i=e[i].from){
int v=e[i].to;
if(vis[v]!=inf) continue;
vis[v]=p;
q.push(make_pair(p,v));
}
}
k=read();
For(i,1,k)
u=read(),printf("%d ",vis[u]==inf ? -1 : vis[u]);
putchar('\n');
}