【題目描述】
爲了幫助警察抓住在逃的罪犯,你發明了一個新的計算機系統。警察控制的區域有N個城市,城市之間有E條雙向邊連接,城市編號爲1到N。警察經常想在罪犯從一個城市逃亡另一個城市的過程中抓住他。偵查員在仔細研究地圖,以決定在哪個城市設置障礙,或者斷掉某條路。
你的計算機系統必須回答以下兩種問題:
1、 如果連接城市G1和G2的路被封掉,罪犯能否從城市A逃到城市B?
2、 如果城市C被封掉,罪犯能否從城市A逃到城市B?
【輸入格式】
輸入第一行包含兩個整數N和E(2<=N<=100000,1<=E<=500000),表示城市和邊的數量。
接下來E行,每行包含兩個不同的整數Ai和Bi,表示城市Ai和Bi之間有一條邊直接相連,任意兩個城市之間最多隻有一條邊相連。
接下來一行包含一個整數Q(1<=Q<=300000),表示詢問次數。
接下來Q行,每行包含4個或5個整數,第一個數爲1或2,表示詢問問題的種類。
如果問題種類是1,後面跟着4個整數A,B,G1,G2,如上描述表示詢問如果G1和G2之間的路封掉罪犯能否從城市A逃到城市B,保證A和B不同,G1和G2之間一定存在路。
如果問題種類是2,後面跟着三個整數A,B,C,三個數互不相同,表示詢問如果封掉城市C,罪犯能否從城市A逃到城市B。
輸入數據保證一開始任意兩個城市都可以相互到達。
【輸出格式】
每個詢問輸出一行“yes”或“no”。
orz:https://blog.csdn.net/qq_36551189/article/details/80657330
【分析】
先做一遍Tarjan依靠時間戳將無向圖中每個點的先後訪問順序求出,按照遍歷順序,將一張圖變成了一棵樹。
對於詢問1:
(設在G1中放置深度較深的節點編號)假如我們想要割去的邊能夠使原先的一個連通分量分成兩個(即G1-G2爲割邊),並且A和B不在同一個連通分量裏,A和B就不能互相到達。
即要滿足如下條件:
1>去掉想要去掉的邊後,A在以G1爲根的子樹裏但是B不在(或者B在以G1爲根的子樹裏但是A不在);
2>以G1爲根的子樹裏沒有點有返祖邊(非樹邊)連向深度比G1淺的點。
對於詢問2:
假如我們想要割去的點能夠使原先的一個連通分量分成多個(即點C爲割點),並且A和B不在去掉後再同一個連通分量裏,A和B就不能互相到達。
即要滿足如下條件:
1>去掉想要去掉的點後,如果A在C的某一個兒子節點的子樹裏(或者B在C的某一個兒子節點的子樹裏)並且A和B不在同一個子樹;
2>在A和B所在的子樹裏沒有點有返祖邊連向深度比C淺的點。
【代碼】
AC次數:2 。(打錯變量)
#include<bits/stdc++.h>
using namespace std;
const int max_n=100000,max_e=500000,max_q=300000;
int N,E,Q,num;
int lin[max_n+10]={},dfn[max_n+10]={},low[max_n+10]={},en[max_n+10]={},ya[max_q+10],yb[max_q+10]={};
struct kk{ int id,next; } e[max_e*2+10]={};
struct K{ int u,v; } ee[max_e+10]={};
struct kkk{ int qu,a,b,c,g,gg; } q[max_e]={};
vector<int> qA[max_n+10],qB[max_n+10],qa[max_n+10],qb[max_n+10];
void insert(int x,int y){ e[++num].next=lin[x]; lin[x]=num; e[num].id=y; }
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++num;//時間戳
for(;qA[x].size();)//當還存在x在詢問時作爲 A 時的情況
{
int X=qA[x].back();
if(dfn[q[X].c])//dfn[q[X].c]已經遍歷過了纔有可能成爲x的父親節點
qa[q[X].c].push_back(X);//qa存要去掉的節點爲q[X].c時的問題編號
qA[x].pop_back();
}
for(;qB[x].size();)//相應的B
{
int X=qB[x].back();
if(dfn[q[X].c])
qb[q[X].c].push_back(X);
qB[x].pop_back();
}
for(int i=lin[x];i;i=e[i].next)
{
if(e[i].id==fa) continue;
if(dfn[e[i].id])
{
low[x]=min(low[x],dfn[e[i].id]);
continue;
}
Tarjan(e[i].id,x);
low[x]=min(low[x],low[e[i].id]);//low記錄所能到達的節點的最小時間戳
for(;qa[x].size();)
{
ya[qa[x].back()]=e[i].id;
qa[x].pop_back();
}//ya[qa[x].back()]代表第qa[x].bcak()個問題中A所在的子樹裏的根節點(刪除x之後)
for(;qb[x].size();)
{
yb[qb[x].back()]=e[i].id;
qb[x].pop_back();//如上
}
}
en[x]=num;//x所在子樹中的最大時間戳
}
bool chek(int x,int y){ return dfn[x]>=dfn[y]&&dfn[x]<=en[y]; }//判斷是否在y爲根節點的子樹裏
int main()
{
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
scanf("%d%d",&N,&E);
for(int i=1;i<=E;i++)
{
scanf("%d%d",&ee[i].u,&ee[i].v);
insert(ee[i].u,ee[i].v);
insert(ee[i].v,ee[i].u);
}
scanf("%d",&Q);
for(int i=1;i<=Q;i++)
{
scanf("%d",&q[i].qu);
if(q[i].qu==1)
scanf("%d%d%d%d",&q[i].a,&q[i].b,&q[i].g,&q[i].gg);
else
{
scanf("%d%d%d",&q[i].a,&q[i].b,&q[i].c);
qA[q[i].a].push_back(i);//qA儲存A的值爲q[i].a的問題編號
qB[q[i].b].push_back(i);//qB儲存B的值爲q[i].b的問題編號
}
}//讀入
num=0;
for(int i=1;i<=N;i++)
if(!dfn[i]) Tarjan(i,0);//
for(int i=1;i<=Q;i++)
{
if(q[i].qu==1)
{
if(q[i].g==q[i].gg) printf("yes\n");
else
{
if(dfn[q[i].g]<dfn[q[i].gg]) q[i].g^=q[i].gg^=q[i].g^=q[i].gg;//交換,使q[i].g所在深度較深
if(dfn[q[i].g]==low[q[i].g])//是否爲割點
if((chek(q[i].a,q[i].g)&&!chek(q[i].b,q[i].g))||(chek(q[i].b,q[i].g)&&!chek(q[i].a,q[i].g)))
{
printf("no\n");
continue;
}
printf("yes\n");
}
}
else
{
if(ya[i]==yb[i]) printf("yes\n");//是否在同一個子樹
else if((ya[i]&&dfn[q[i].c]<=low[ya[i]])||(yb[i]&&dfn[q[i].c]<=low[yb[i]])) printf("no\n");
else printf("yes\n");
}
}
return 0;
}
PS:請注意代碼細節(下標,傳參等)