一本通->提高篇->圖論->割點和橋:

一本通:
提高篇:
圖論:
割點和橋:

1520:【 例 1】分離的路徑
題意:如何把有橋圖通過加邊變成邊雙連通分量
如果葉子數(縮點後度爲1的點)爲1,則至少需要添加0條邊;
否則爲(葉子數+1)/ 2;

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+100;
int n,m,cnt=1; //cnt從1開始,保證取反邊 
int head[N],ver[N],nex[N],colour,top;
int dfn[N],low[N],dfstime,vis[N],du[N],col[N];
stack<int> st;
void add(int a,int b){
	ver[++cnt] = b;
	nex[cnt] = head[a];
	head[a] = cnt;
}
void read(){
	memset(head,-1,sizeof head);
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
}
void tarjan(int x){
	dfn[x] = low[x] = ++dfstime;
	st.push(x);
	for(int i=head[x];~i;i=nex[i]){
		int y = ver[i];
		if(!vis[i^1]){
			vis[i] = 1;
			if(!dfn[y]){
				tarjan(y);
				low[x] = min(low[x],low[y]);
			}else low[x] = min(low[x],dfn[y]);
		}else vis[i] = 1;
	}
	int i;
	if(low[x] == dfn[x]){//橋不可能在多個橋雙連通裏,而點可能在多個點雙連通裏 
		colour++;
		do{
			i=st.top();st.pop();
			col[i]  = colour; //通橋雙連通染色 
		}while(i!=x);
	}
}
void solve(){	
	//tarjan(1);
	for(int i=1;i<=n;i++)
	if(!dfn[i]) tarjan(i);
	
	for(int x=1;x<=n;x++){
		for(int i=head[x];~i;i=nex[i]){
			if(vis[i^1]){ //樹枝邊,因爲dfs不考慮雙向邊 
				vis[i] = 0;
				int y=ver[i];
				if(col[y] != col [x]){
					du[col[x]]++; //找到葉子節點 
					du[col[y]]++;
				}
			}else vis[i] = 0;
		}
	}
	int ans = 0;
	for(int i=1;i<=colour;i++){
		if(du[i]==1) ans++;
	}
	printf("%d\n",(ans+1)/2);//新建道路的數目是把原橋刪去後橋雙連通縮成點後 把每兩個葉子上連邊即可 
	//結論:當葉子節點=1,要+的邊爲0,否則爲(葉子數+1)/2 
}
int main(){
	read();
	solve();
	return 0;
} 

1521:【 例 2】礦場搭建
點雙連通 + 分析
具體三種情況
1:特殊情況,只有一個雙連通分量,割點爲0,答案爲cn2c_n^2
2:一般情況,一個雙連通分量若只有一個割點,在非割點處任意選擇一個點建通道。
3:割點大於等於2,不需要再建立通道。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e3;
int n,m,cs,cnt,root,dfstime,bt;
int nex[N],head[N],ver[N];
int low[N],dfn[N],cut[N];
vector<int> block[N];
stack<int> st;
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void read(){
		for(int i=0;i<N;i++) cut[i] = dfn[i]=low[i]=nex[i] = ver[i] = 0,head[i] = -1;
		bt = dfstime = cnt = 0;
		while(st.size()) st.pop();
		for(int i = 1,x,y;i <= m;i ++){
			scanf("%d%d",&x,&y);
			n = max(n,max(x,y));//題目未給出最大點數 
			add(x,y);add(y,x);
		}	
}
void tarjan(int x,int root){
	dfn[x] = low[x] = ++dfstime;
	int tot = 0;
	st.push(x);
	for(int i=head[x];~i;i=nex[i]){
		int y = ver[i];
		if(!dfn[y]){
			tot++;
			tarjan(y,root);
			low[x] = min(low[x],low[y]);
			if((x==root&& tot>1) || (x!=root && dfn[x]<=low[y] )) cut[x] = 1;//求割點的兩種判斷 
			if(dfn[x] <= low[y]){
				bt ++;
				block[bt].clear();
				int top;
				do{
					top = st.top();st.pop(); 
					block[bt].push_back(top);
				}while(y!=top);
				block[bt].push_back(x);  //重點:區分於橋雙,點雙的點可能多次出現在點雙連通中 
			}
		}else{
			low[x] = min(low[x],dfn[y]);
		}
	}
}
void solve(){
	ll res = 0,num = 1;
	for(int i=1;i<=bt;i++){
		int gdnum = 0;
		int len = block[i].size();
		for(int j =0;j<len;j++){
			if(cut[block[i][j]]==1)
				gdnum ++;
		}
		if(gdnum==0) res+=2,num = num*(len-1)*len/2;
		else if(gdnum==1) res++,num=num*(len-1);
	}
	printf("%lld %lld\n",res,num);
}
int main(){
	while(~scanf("%d",&m) && m){
		printf("Case %d: ",++cs);
		read();
		for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i,i);	
		solve();
	}
	return 0;
} 

1522:網絡
割點模板

#include<bits/stdc++.h>
using namespace std;
const int N = 1e4,M = 1e6+7;
int n,m,ans;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void read(){
	int x; char y;
	memset(head,-1,sizeof head);
	for(int i=0;i<N;i++)
	low[i] = dfn[i] = cut[i] = 0;
	dfstime = cnt = ans = 0;
	string line;
	while(getline(cin,line)){
		stringstream ss(line);
		int i=0,x,u,y;
		while(ss>>u){
			if(u==0) return;
			if(i==0) x = u;
			else{
				add(x,u);add(u,x);
			}
			 i++; 
		}	
	}
}
void tarjan(int x,int root){
	dfn[x] = low[x] = ++dfstime;
	int tot = 0;
	for(int i=head[x];~i;i=nex[i]){
		int y = ver[i];
		if(!dfn[y]){
			tot++;
			tarjan(y,root);
			low[x] = min(low[x],low[y]);
			if((x==root && tot>1) || (x!=root && low[y]>= dfn[x])){
				cut[x] = 1;
			}
		}else{
			low[x] = min(low[x],dfn[y]);
		}
	}
	
}
void solve(){
	for(int i=1;i <= n;i++)
	if(!dfn[i]) tarjan(i,i);
	
	for(int i=1;i <= n;i++)
	if(cut[i]==1) ans ++;
	printf("%d\n",ans);
}
int main(){
	while(scanf("%d",&n) && n){
		read();
		solve();
	}
	return 0;
}

1523:嗅探器
找割點 + 分析
1:這個割點必須是從a到b的必經點,所以不可以是 a 或者 b。
2:另一個服務器是否在路徑上,也就是b 和 a 要連通, 即 low[b] >= dfn[a]。
3:這個點是在b之前,即dfn[t o[x] ] <= dfn[b], a之後(我們從a開始就保證了)。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5,M = 1e6+7,INF = 0x3f3f3f3;
int n,m,ans=INF;
int a,b;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void read(){
	scanf("%d",&n);
	int x,y;
	while(scanf("%d%d",&x,&y) && x+y!=0){
		add(x,y);add(y,x);
	}
	scanf("%d%d",&a,&b);
}
void tarjan(int x,int root){
	dfn[x] = low[x] = ++dfstime;
	int tot = 0;
	for(int i=head[x];i;i=nex[i]){
		int y = ver[i];
		if(!dfn[y]){
			tot++;
			tarjan(y,root);
			low[x] = min(low[x],low[y]);
			/*if(x!=a&&x!=b)//不是起點終點
            if(dfn[x]<=low[y]&&dfn[y]<=dfn[b]&&low[b]>=dfn[a])
            ans = min(ans,x);*/
			if(low[y] >= dfn[x] && low[b] >= dfn[a] && dfn[y] <= dfn[b] && x!=a && x!=b){
				ans = min(ans,x);
			}
		}else{
			low[x] = min(low[x],dfn[y]);
		}
	}
}
int main(){
	read();
	tarjan(a,a);
	if(ans!=INF) printf("%d\n",ans);
	else puts("No solution");
	return 0;
}

1524:旅遊航道
橋模板

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5,M = 1e6+7,INF = 0x3f3f3f3;
int n,m,ans;
int tot,dfn[N],low[N],dfstime;
int cnt,head[M],nex[M],ver[M];
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void read(){
	memset(head,-1,sizeof head);
	for(int i=0;i<N;i++)
	dfn[i] = low[i] = 0;
	ans = cnt = 0;
	int x,y;
	for(int i = 1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
}
int vis[N];
void tarjan(int x,int fa){
	dfn[x] = low[x] = ++dfstime;
	for(int i=head[x];~i;i=nex[i]){
		int y = ver[i];
		if(dfn[y] && y!=fa) low[x] = min(low[x],dfn[y]);
		if(!dfn[y]){
			tarjan(y,x);			
			if(dfn[x] < low[y]) //滿足條件 
			ans++;		
			low[x] = min(low[x],low[y]);
		}
	}
}
int main(){
	while(~scanf("%d%d",&n,&m) && n+m){
	read();
	for(int i=1;i<=n;i++)
	if(!dfn[i]) tarjan(i,i);
	printf("%d\n",ans);		
	}
	return 0;
}

1525:電力
找割點
1:求割點後最多能產生多少分量:
這題關鍵點就是i == root && son==1 的情況,這種情況如果是沒有割點的,但是切除根節點後任然會被分出一個節點,所以我們針對這種情況改變代碼。
如果原始cut[i]=0,表示i是孤立的一點,此時cut[i]-1=-1.
如果原始cut[i]=1,表示i爲根且有一個兒子,此時cut[i]-1=0.
如果原始cut[i]>=2,表示i爲根且分割了>=2個兒子,此時cut[i]-1>=1.

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e4+7,M = 1e5+7;
typedef long long ll;
int n,m;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void read(){
	cnt = dfstime = 0;
	memset(head,-1,sizeof head);
	for(int i=0;i < N;i++)
	cut[i] = dfn[i] = low[i] = 0;
	for(int i=1; i <= m ;i ++){
		int x,y;scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
}
void tarjan(int x,int root){
	dfn[x] = low[x] = ++dfstime;
	int tot = 0;
	for(int i=head[x];~i;i=nex[i]){
		int y = ver[i];
		if(!dfn[y]){
			tot++;
			tarjan(y,root);
			low[x] = min(low[x],low[y]);
			/*if((x==root && tot>1) || (x!=root && low[y]>= dfn[x])){
				cut[x] ++; //x能夠割出多少分量 
			}*/
			if(low[y] >= dfn[x]) cut[x] ++;
		}else {
			low[x] = min(low[x],dfn[y]);
		}
	}
}
int main(){
	while(scanf("%d%d",&n,&m) && n+m !=0){
		read();
		int sum = 0,ans = -10000;
		for(int i=0; i < n;i++)
		if(!dfn[i]) tarjan(i,i),sum++,cut[i]--;
		for(int i=0;i<n;i++)
		ans = max(ans,cut[i]);
		printf("%d\n",ans+sum);
	}
	return 0;
}

1526:Blockade**
新開一篇講解

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7,M = 1e6+7;
typedef long long ll;
int n,m;
int cnt,head[M],nex[M],ver[M];
int low[N],dfn[N],dfstime,cut[N];
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void read(){
	scanf("%d%d",&n,&m);
	for(int i=1; i <= m;i++){
		int x,y;scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
}
int size[N]; ll ans[N];
void tarjan(int x,int root){
	dfn[x] = low[x] = ++dfstime;
	int tot = 0;
	size[x] = 1;
	ll sum = 0;
	for(int i=head[x];i;i=nex[i]){
		int y = ver[i];
		if(!dfn[y]){
			tot++;
			tarjan(y,root);
			low[x] = min(low[x],low[y]);
			size[x] += size[y]; //記錄x的子樹節點個數 
			if((x==root && tot>1) || (x!=root && low[y]>= dfn[x])){
				cut[x] = 1;
				ans[x] += size[y]*sum;  //所有子樹能提供的邊數 
										//x的子樹size[y]能提供的個數*前面已經提供了同屬於x子樹的個數和 
				sum += size[y];//sum加上這顆子樹點 
			}
		}else{
			low[x] = min(low[x],dfn[y]);
		}
	}
	ans[x] += (n-1-sum)*sum+(n-1); //父親塊能提供的邊數 + x自己能提供的邊數 
}
int main(){
	read();
	for(int i=1;i<=n;i++)
	if(!dfn[i]) tarjan(i,i);
	
	for(int i=1;i <= n;i++){
		printf("%lld\n",ans[i]*2);
	}
	return 0;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章