圖論基礎問題【筆記】

主要集中一些初學圖論的基礎問題

第一題 洛谷P3916 圖的遍歷

題目描述

給出N個點,M條邊的有向圖,對於每個點v,求A(v)表示從點vv出發,能到達的編號最大的點。

sample input
4 3
1 2
2 4
4 3
sample output
4 4 3 4
思路

建圖很簡單,但是要到達最大的點,dfs每個點那麼會T,最好的辦法是反向存圖,看最大的邊可以到達哪些點並進行標記就行了

代碼片
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

#define MAXL 100010

int N, M, A[MAXL];
vector<int> G[MAXL]; //vector存圖 

void dfs(int x, int d) //d保留最大值,x表示現在的結點
{
    if(A[x]) return; //訪問過 
    A[x] = d;
    for(int i=0; i<G[x].size(); i++)
        dfs(G[x][i], d);
}

int main() 
{
    int u, v;
    scanf("%d%d", &N, &M);
    for(int i=1; i<=M; i++) {
        scanf("%d%d", &u, &v);
        G[v].push_back(u); //反向建邊 
    }
    for(int i=N; i; i--) dfs(i, i); 
    for(int i=1; i<=N; i++) printf("%d ", A[i]);
    printf("\n");
    return 0;
}

關於歐拉路的題目放兩道
證明歐拉路和歐拉回路:
奇點:度數爲奇數的點
歐拉路:只有兩個奇點連通的圖在這裏插入代碼片
歐拉回路:沒有奇點連通的圖
求歐拉路:先判斷奇點,再dfs
歐拉回路:對於任意一個點執行dfs
歐拉路:對奇點執行dfs

第二題 洛谷P1341 無序字母對

題目描述

給定n個各不相同的無序字母對(區分大小寫,無序即字母對中的兩個字母可以位置顛倒)。請構造一個有n+1個字母的字符串使得每個字母對都在這個字符串中出現。

輸入格式

第一行輸入一個正整數n。

以下n行每行兩個字母,表示這兩個字母需要相鄰。

輸出格式

輸出滿足要求的字符串。

如果沒有滿足要求的字符串,請輸出“No Solution”。

如果有多種方案,請輸出前面的字母的ASCII編碼儘可能小的(字典序最小)的方案

sample input

4
aZ
tZ
Xt
aX
sample output
XaZtX
思路

本題其實問的是:給出一個無向圖,求字典序最小的一條歐拉路徑(自己可以畫一個圖就容易懂了)
注意點:
1、是否是連通圖,用並查集判斷,只有一個根
2、是否是歐拉路或者歐拉回路,判斷奇點個數
3、dfs求歐拉路徑

代碼片(結合思路註釋)

#include<bits/stdc++.h>
using namespace std;
//ascii不大於128,所以取130
const int N=52*51/2+10;    //每個字母可以和51個字母想連,所以字符個數爲52*51/2
int f[130];   //並查集父結點,檢查根的個數
int m[130][130],d[130];     //數據量不大,m爲鄰接矩陣,d記錄度數
char ans[N];       //記錄輸出的字符串
int n;

void init() {         //初始化
	for(int i=64; i<130; i++ ) f[i]=i;
	memset(m,0,sizeof(m));
	memset(d,0,sizeof(d));
}

int find(int x) {       
	return x==f[x]?x:f[x]=find(f[x]);
}

void unions(int x, int y) {
	int fx=find(x);
	int fy=find(y);
	if ( fx!=fy ) f[fx]=fy;
}

void dfs(int head) {          //路徑搜索
	for(int i=64; i<130; i++ ) {
		if ( m[head][i] ) {
			m[head][i]=m[i][head]=0;
			dfs(i);
		}
	}
	ans[n--]=head;   //在回溯部分記錄,因此需要倒序輸出
}

int main() {
	char s[2];
	scanf("%d",&n);
	init();      //初始化
	for(int i=0; i<n; i++ ) {
		scanf("%s",s);
		m[s[0]][s[1]]=m[s[1]][s[0]]=1;       //記錄鄰接矩陣
		unions(s[0],s[1]);       //記錄父結點
		d[s[0]]++;      //度數
		d[s[1]]++;
	}
	int cnt=0;
	for(int i=64; i<130; i++ ) {      
		if ( d[i] && i==find(i) ) cnt++;        //記錄根的個數
	}
	if ( cnt!=1 ) {           //超過一個根就不是連通圖了,第一個步驟
		printf("No Solution\n"); return 0;
	}
	int head=0;
	cnt=0;
	for(int i=64; i<130; i++ ) {
		if ( d[i]%2 ) {      //記錄奇點個數
			cnt++;
			if ( !head ) head=i;    //記錄第一個,這樣就是最小字符起頭了
		}
	}
	if ( cnt>0 && cnt!=2 ) {    //奇點個數不是0或者2,第二個步驟
		printf("No Solution\n"); return 0;
	}
	if ( !head ) {       //如果是歐拉回路需要找到第一個頭
		for(int i=64; i<130; i++ ) {
			if ( d[i] ) { head=i; break;}
		}
	}
	ans[n+1]='\0';
	dfs(head);        //dfs深搜答案了,第三個步驟
	printf("%s\n",ans);
	return 0;
}

題目三 洛谷P1127 詞鏈

題目描述

有n個單詞,若單詞的首字母等於另一個單詞的尾字母,就可以連起來,判斷所給單詞是否能夠連起來,能的話輸出字典序最小的詞鏈,不能輸出"***".
範圍:n<1000

樣例
6
aloha
arachnid
dog
gopher
rat
tiger
aloha.arachnid.dog.gopher.rat.tiger

思路

這一題範圍很小,直接dfs。
有幾個點需要注意:
1、比較困難的點是尋找第一個單詞,接下來就是方法:
通過舉例來理解
有三個字符串 acm,mo,orz.我們可以發現‘m’和‘o’都是可以對應的,再看看樣例,就會有以下發現:第一個單詞的首字母的數目,永遠比所有單詞的最後一個相同字母的數目多一(我知道有點難理解)。舉個例子:樣例中第一個單詞的首字母’a’的出現次數是2次,而所有單詞中末尾字母’a’出現的次數只有一次,而其它的字母都一一對應的出現了1次!
那就用兩個數組來存儲就行了。

代碼片
#include<bits/stdc++.h>
using namespace std;

const int N=1010;
string str[N];//輸入
int s1[130],s2[130];//s1存儲首字母個數,s2存儲末尾字母個數
bool v[N]={0};//判重,dfs防止遍歷相同的單詞
bool flag=0;//用來判斷是否完成dfs的
string ans[N];//記錄答案
int n;

void dfs(int p, int num) {//p表示pos,現在記錄單詞的位置,num記錄第幾個單詞
	if ( flag ) return;
	if ( num==n ) {//找到最後一個,存完就結束搜索了
		ans[num]=str[p];
		v[p]=1;
		flag=true;
		return;
	}
	v[p]=1;//對存入的標記,避免再次遍歷
	ans[num]=str[p];//記錄答案
	for(int i=0; i<n; i++ ) {
		if ( v[i] ) continue;
		int len=str[p].length();
		if ( str[p][len-1]==str[i][0] ) {//兩個單詞頭尾之間字母是否一樣
			dfs(i,num+1);
			if (flag) return;
			v[i]=0;//沒找到回溯
		}
	}
}

int main() {
	scanf("%d",&n);
	for(int i=0; i<n; i++ ) {
		cin>>str[i];
		int len=str[i].length();
		s1[str[i][0]]++;//記錄首字母
		s2[str[i][len-1]]++;//記錄末字母
	}
	sort(str,str+n);//因爲要按照最小的字典序輸出,所以要sort一下
	int start=0;//標記第一個字符串的位置
	for(int i=0; i<n; i++ ) {
		//下面的這個if就是注意點了,對首字母(s1)和尾字母(s2)的查找找到第一個單詞
		if ( s1[str[i][0]] && s2[str[i][0]] && s1[str[i][0]]-s2[str[i][0]]==1 ) { 
			start=i;
			break;
		}
	}
	dfs(start,1);//深搜答案
	if (!flag) printf("***\n");
	else {
		for(int i=1; i<=n; i++ ) {
			if ( i!=n ) cout<<ans[i]<<".";
			else cout<<ans[i]<<endl;
		}
	}
	return 0;
}

題目四 洛谷P1330 封鎖陽光大學

題目描述

一個由n個點m條邊構成的無向圖,每個點被路障(有兩種類型的路障)進行封鎖,對封鎖有以下要求
相鄰的兩點封鎖的路障不能相同
不可以輸出Impossible 可以的話輸出最少的路障數

樣例
3 3
1 2
1 3
2 3
Impossible
3 2
1 2
2 3
1
思路

1、記錄每個點的路障種類,若這個點已有路障且路障種類不同,那就是錯的。
2、記錄兩種路障所需要的數目。
3、因爲可能出現多個地圖,所以要遍歷一遍,但對已經遍歷過的點就不用重複了

代碼片
#include<bits/stdc++.h>
using namespace std;

const int N=1e4+10;
int v[N]={0};//判斷是否訪問過.同時用來記錄路障,一種是1,一種是-1
int sum[2];//統計數目 
vector<int> q[N];//建圖 

bool dfs(int x, int color) {       //標色 
	if ( v[x] ) {
		if ( v[x]!=color ) return false;//(思路1)
		return true;
	}
	v[x]=color;
	color==1?sum[1]++:sum[0]++;//記錄路障種類數(思路2)
	bool flag=true;
	for(int i=0; i<q[x].size() ; i++ ) {
		if( !flag ) break;
		flag=dfs(q[x][i],-color);
	}
	return flag;
}

int main() {
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=0; i<m; i++ ) {//建圖 
		int x,y;
		scanf("%d %d",&x,&y);
		q[x].push_back(y);
		q[y].push_back(x); 
	}
	int ans=0;
	for(int i=1; i<=n; i++ ) {    //可能存在多個圖 (思路3)
		if ( v[i] ) continue;    //已經遍歷過的就跳過 
		sum[0]=sum[1]=0;
		if ( !dfs(i,1) ) {
			printf("Impossible\n");
			return 0;
		}
		ans+=min(sum[0],sum[1]);
	}
	printf("%d\n",ans);
	return 0;
} 

題目五 P2661 信息傳遞

題目描述

有 n個同學(編號爲 1 到 n )正在玩一個信息傳遞的遊戲。在遊戲裏每人都有一個固定的信息傳遞對象,其中,編號爲 i 的同學的信息傳遞對象是編號爲 Ti的同學。遊戲開始時,每人都只知道自己的生日。之後每一輪中,所有人會同時將自己當前所知的生日信息告訴各自的信息傳遞對象(注意:可能有人可以從若干人那裏獲取信息, 但是每人只會把信息告訴一個人,即自己的信息傳遞對象)。當有人從別人口中得知自 己的生日時,遊戲結束。請問該遊戲一共可以進行幾輪?
輸入:n個人,每個人告訴的人,n<200000

樣例
5
2 4 2 3 1
3
思路

這題建模來看就是求最小環,可以用並查集也可以用dfs
dfs思路:到過一個環的點記錄步數,如果再次走到這個點上,就可以判斷出成環,當前步數減去剛纔到該點步數就是這個環長

#include<bits/stdc++.h>
using namespace std;

#define INF 0x3f3f3f3f
const int N=200000+10;
int a[N],num[N];//a記錄指向點,num記錄到達該點需要的長度
bool v[N];//防止再次遍歷
int ans;

void dfs(int x, int s) {//x表示所在點,s表示到這邊需要的步數
	if ( v[x] ) return;
	if ( num[x] ) ans=min(ans,s-num[x]);
	else {
		num[x]=s;//先記錄
		dfs(a[x],s+1);
		v[x]=1;//一定要後標記遍歷
	}
}

int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++ ) scanf("%d",&a[i]);
	ans=INF;
	for(int i=1; i<=n; i++ ) dfs(i,0);  //數字爲步數,多個環遍歷
	printf("%d\n",ans);
	return 0;
}

題目六 P2853 牛的野餐 cow picnic

題目描述

K(1≤K≤100)只奶牛分散在N(1≤N≤1000)個牧場.現在她們要集中起來進餐.牧場之間有M(1≤M≤10000)條有向路連接,而且不存在起點和終點相同的有向路.她們進餐的地點必須是所有奶牛都可到達的地方.那麼,有多少這樣的牧場呢?

樣例
2 4 4
2
3
1 2
1 4
2 3
3 4
2
思路

如果對每個點都遍歷過去的話,回T。其實只要對牛所在的點進行遍歷就行了,每次遍歷到的點在那個地方的num++,最後統計幾個地方數量相等就好,簡單題

代碼片
#include<bits/stdc++.h>
using namespace std;

const int N=1010;
vector<int> q[N];
int num[N];//記錄每塊地方牛的數量
bool v[N];//每次都要進行判斷
int a[N]={0};

void dfs(int p) {
	v[p]=1;
	num[p]++;
	for(int i=0; i<q[p].size(); i++ ) {
		if ( !v[q[p][i]] ) dfs(q[p][i]);
	}
}

int main() {
	int k,n,m;
	cin>>k>>n>>m;
	for(int i=1; i<=k; i++ ) {
		cin>>a[i];
	}
	for(int i=1; i<=m; i++ ) {
		int x,y;
		cin>>x>>y;
		q[x].push_back(y);
	}
	for(int i=1; i<=k; i++ ) {
		for(int j=1; j<=n; j++ ) v[j]=0; //避免無向圖點的出現,不然要MLE的
		dfs(a[i]);
	}
	int cnt=0;
	for(int i=1; i<=n; i++ ) {
		if ( num[i]==k ) cnt++;
	}
	cout<<cnt<<endl;
	return 0;
}

題目七 P1363 幻象迷宮

題目描述

幻象迷宮可以認爲是無限大的,不過它由若干個N*M的矩陣重複組成。矩陣中有的地方是道路,用’.‘表示;有的地方是牆,用’#‘表示。LHX和WD所在的位置用’S’表示。也就是對於迷宮中的一個點(x,y),如果(x mod n,y mod m)是’.‘或者’S’,那麼這個地方是道路;如果(x mod n,y mod m)是’#’,那麼這個地方是牆。LHX和WD可以向上下左右四個方向移動,當然不能移動到牆上。

請你告訴LHX和WD,它們能否走出幻象迷宮(如果它們能走到距離起點無限遠處,就認爲能走出去)。如果不能的話,LHX就只好啓動城堡的毀滅程序了……當然不到萬不得已,他不想這麼做。。。

樣例

多組輸入輸出,n,m小於1500

5 4
##.#
##S#
#..#
#.##
#..#
5 4
##.#
##S#
#..#
..#.
#.##
Yes
No
思路

因爲是走到無窮遠的,x,y取模表示走到無窮遠相同地圖的位置。判斷一下這個點是否走到過,且此時的座標是否相等,如果相等就代表還是在同一張地圖裏,如果不相等就說明走到了這個點的無窮遠。

其實就是問一下這張圖上能否連通
代碼片(內含詳細註釋)
#include<bits/stdc++.h>
using namespace std;


const int N=1500+10;
const int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//四個方向行走 
int n,m;
int st_x,st_y;//起始位置 
int vis[N][N][3];//第一維記錄有無被訪問,第二維記錄被訪問時橫座標,第三維縱座標 
bool fl,a[N][N];//a用來建立地圖 ,1爲牆0爲路 ;fl用來判斷能否走到無窮遠 
char ch;

void dfs(int x, int y, int lx, int ly) { 
//x,y爲取模的座標,沒有取模的座標lx,ly 
//第一次走這個迷宮,x=lx,y=ly;只要走到的一個點,x!=lx||y!=ly,
//那麼這個點走了兩遍 
	if( fl ) return;//可以走到無窮遠了
	if ( vis[x][y][0] && ( vis[x][y][1]!=lx || vis[x][y][2]!=ly ) ) {
		fl=1;
		return;
	} 
	vis[x][y][1]=lx,vis[x][y][2]=ly,vis[x][y][0]=1;
	for(int i=0; i<4; i++ ) {
		int xx=(x+dir[i][0]+n)%n,yy=(y+dir[i][1]+m)%m;
		int lxx=lx+dir[i][0],lyy=ly+dir[i][1];
		if ( !a[xx][yy] ) {
			if ( vis[xx][yy][1]!=lxx || vis[xx][yy][2]!=lyy || !vis[xx][yy][0] )
			dfs(xx,yy,lxx,lyy);
		}
	}
}

int main() {
    ios::sync_with_stdio(false);
	while(cin>>n>>m) {
		fl=0;
		memset(a,0,sizeof(a));
		memset(vis,0,sizeof(vis));
		for(int i=0; i<n; i++ ) {
			for(int j=0; j<m; j++ ) {
				cin>>ch;
				if ( ch=='#' ) a[i][j]=1;
				if ( ch=='S' ) st_x=i,st_y=j;
			}
		}
		dfs(st_x,st_y,st_x,st_y);
		if ( fl ) puts("Yes");
		else puts("No"); 
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章