【COCI2007】追捕盜賊

【題目描述】

  爲了幫助警察抓住在逃的罪犯,你發明了一個新的計算機系統。警察控制的區域有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:請注意代碼細節(下標,傳參等)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章