P8943 Deception Point 题解
Description
题目给的很详细了。
Solution
首先 \(n\) 个点 \(n\) 条边,我们很容易就想到基环树(比正常的树多了一条边,形成了一个环),不会也没关系,这题跟基环树其实关系不大。
首先,我们可以发现题目中说明了这个环不是一个四元及以下的环,这代表着如果 \(A\) 提前进入了这个环,那么他就可以和 \(B\) 周旋,这样 \(B\) 就永远都抓不到 \(A\) ;相反的,如果 \(B\) 赶在 \(A\) 之前就把 \(A\) 通往环内的那个点就封死了,那么 \(A\) 就只能静待被抓了。
想到这里其实思路就很明了了,我们需要预处理出这些东西:
-
求出这个环;
-
求出每个不在环上的点到环的距离以及最初到环上的哪个点;
-
求出环内点与点之间的距离。
由于赛前几天一直在学习边双,降智了,所以找环我用的边双做的(,其实也可以用拓扑等方法来解决;
对于第二个问题,我们 \(O(n)\) 枚举每一条边,如果出现了点 \(x\) 在环内点 \(y\) 不在环内的情况,我们从 \(y\) 开始进行广搜给 \(y\) 延伸出来的子树打上标记。
对于第三个问题,我们随便断掉环上的一条边,这样形成的就是一条链,我们在链上以断的那条边的其中一个端点为起点再进行一次广搜,处理出环上每个点到这个端点的距离,那么最后环上两个点 \(x,y\) 的最短距离就是 \(\min(\lvert len_x-len_y \rvert,size- \lvert len_x-len_y \rvert)\),其中 \(size\) 表示的是这个环的大小。
由此可知,总复杂度是一个线性的,可以通过。
Code
#include<bits/stdc++.h>
//define int long long
#define ll long long
#define next nxt
#define re register
#define il inline
const int N = 4e5 + 5;
using namespace std;
int max(int x,int y){return x > y ? x : y;}
int min(int x,int y){return x < y ? x : y;}
int n,q,u,v,cnt,tot,idx,ans,k,cutx,cuty;
int low[N],dfn[N],stk[N],belong[N],len[N];
struct Node{
int tp,dep;//tp表示这个不在环上的点通过哪个点进入环,以及到环的距离
}f[N];
vector <int> vec[N];
struct node{
int u,v,next;
}edge[N<<1]; int head[N],num_edge;
il int read()
{
int f=0,s=0;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) f |= (ch=='-');
for(; isdigit(ch);ch=getchar()) s = (s<<1) + (s<<3) + (ch^48);
return f ? -s : s;
}
il void add(int from,int to)
{
edge[++num_edge] = (node){from,to,head[from]};
head[from] = num_edge;
}
il void tarjan(int x,int in_edge)//边双缩点
{
dfn[x] = low[x] = ++tot;
stk[++idx] = x;
for(re int i=head[x];i;i=edge[i].next)
{
int y = edge[i].v;
if(!dfn[y])
{
tarjan(y,i);
low[x] = min(low[x],low[y]);
}
else if(i != (in_edge^1)) low[x] = min(low[x],dfn[y]);
}
if(dfn[x] == low[x])
{
++cnt;
while(x != stk[idx+1])
{
vec[cnt].push_back(stk[idx]);
belong[stk[idx]] = cnt;
idx--;
}
}
}
il void bfs(int tp,int x,int dep)//求不在环上的点从哪个点入环,路径长度是多少
{
f[x] = (Node){tp,dep};
queue <int> q;
q.push(x);
while(!q.empty())
{
u = q.front(); q.pop();
for(re int i=head[u];i;i=edge[i].next)
{
v = edge[i].v;
if(v == tp) continue;
if(!f[v].dep)
{
f[v].dep = f[u].dep + 1 , f[v].tp = tp;
q.push(v);
}
}
}
}
il void circle()//求断边后链上路径长
{
memset(len , -1 , sizeof len);
len[cutx] = 0;
queue <int> q;
q.push(cutx);
while(!q.empty())
{
u = q.front(); q.pop();
for(re int i=head[u];i;i=edge[i].next)
{
v = edge[i].v;
if((u == cutx && v == cuty) || (v == cutx && u == cuty)) continue;
if(belong[v] != k) continue;
if(len[v] == -1)
{
len[v] = len[u] + 1;
q.push(v);
}
}
}
}
signed main()
{
n = read() , q = read();
num_edge = 1;
for(re int i=1;i<=n;i++)
{
u = read() , v = read();
add(u,v) , add(v,u);
}
tarjan(1,1);//边双缩点找环
for(re int i=1;i<=cnt;i++) if(vec[i].size() > 1) { k = i; break; }//找哪个是环,步骤一
for(re int i=2;i<=num_edge;i++)
{
u = edge[i].u , v = edge[i].v;
if(belong[u] == belong[v]) { cutx = u , cuty = v ; continue; }//随便找一条在环上的边
if(belong[u] == k && belong[v] != k) bfs(u,v,1);//一个在环上一个不在环上,进行步骤二
}
circle();//步骤三
int siz = vec[k].size() , disu , disv;
while(q--)
{
u = read() , v = read();
if(belong[u] == k) { cout << "Survive" << "\n"; continue; }//原本就在环里那直接跑了
disu = f[u].dep;//计算路径长度
if(belong[v] == k) disv = min(abs(len[f[u].tp]-len[v]),siz-abs(len[f[u].tp]-len[v]));//B要拦截的距离就是B到环的距离B的tp和A的tp之间的距离
else disv = f[v].dep + min(abs(len[f[u].tp]-len[f[v].tp]),siz-abs(len[f[u].tp]-len[f[v].tp]));
if(disu < disv) cout << "Survive" << "\n";
else cout << "Deception" << "\n";
}
return 0;
}