图论基础问题【笔记】

主要集中一些初学图论的基础问题

第一题 洛谷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
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
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
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
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"); 
	}
}

你可能感兴趣的:(图)