ACM-ICPC寒假算法训练4:图论1(图的遍历)

题1:*阳光大学:洛谷1330

算法分析:

首先这个图,可能不连通,我们针对其中一个联通分量来看:如果我们在一个点放下了一只河蟹,那么那个点与之邻接的所有点都不可以再放河蟹。那么对于一个点,我们就有两个策略:放河蟹与不放河蟹。这也就对应了一个节点的两个状态:0(不放)或者1(放)。我们可以以这个联通分量的任意一个点作为深度搜索的起点,然后进行深度遍历,我们遍历的目的其实只是为了检测在图被 0/1点铺满的时候,会不会有任意一条边的两个点都是 0的情况,如果有这种情况,那么就是Impossible了!因为这样就有一条边未被*。
那如果可以被*,我们怎么计算最少需要放多少个河蟹呢?对于一个联通子图,我们需要统计放 1的点的个数和放 0的点的个数的最小值,因为放 1的点和 0的点其实状态可以互换,所有我们选择其中的最小值即可。

AC代码:

#include <cstdio>
#include <vector>
using namespace std;

const int maxn = 1e4 + 5;
vector<int> G[maxn];
int n, m, a, b, ans, vis[maxn], cnt[2], col[maxn];// col[] 数组存下每个点的状态,以便检查合法性 
inline int Min(int x, int y) { return x < y ? x : y; } 
bool dfs(int p, int c){
	if(vis[p]){
		if(c == col[p])
			return true;
		else
			return false;	 
	}// 新遍历的点已经被访问过了,最后一步合法说明这个联通分量合法 
	vis[p] = 1, col[p] = c, cnt[c]++;
	for(int i = 0;i < (int)G[p].size();++i)
		if(!dfs(G[p][i], c ^ 1))
			return false;
	return true;
}

int main(){
	scanf("%d %d", &n, &m);
	while(m--){
		scanf("%d %d", &a, &b);
		G[a].push_back(b);
		G[b].push_back(a);
	}
	for(int i = 1;i <= n;++i){
		cnt[0] = cnt[1] = 0;
		if(!vis[i]){
			if(!dfs(i, 0)){
				printf("Impossible");
				return 0;
			}
			else
				ans += Min(cnt[0], cnt[1]);
		}
	}
	printf("%d", ans);
	return 0;
} 

题2:图的遍历:洛谷3916

算法分析:

这一题乍一看感觉这数据规模暴力搜DFS貌似过不去啊???
对 1-n 的每一个点求这个点能到达的编号最大的点,这个感觉会有很多无用的搜索啊。我们试着把样例画一下,发现过程如下:
1 -> 2 -> 4 ->3
于是输出: 4 4 3 4
那么我们不如反过来,反向建边,从大的编号节点开始深度遍历,因为与较大节点能够可达的节点的结果一定是这个较大节点,如果不能与较大节点可达,那么它的值一定就小于较大节点!

AC代码:

#include <cstdio>
#include <vector>
using namespace std;

const int maxn = 1e5 + 5;
vector<int> G[maxn];
int n, m, a, b, ans[maxn], vis[maxn];
void dfs(int p, int k){
	if(vis[p])	return ;
	vis[p] = 1;
	ans[p] = k;
	for(int i = 0;i < (int)G[p].size();++i)
		if(!vis[G[p][i]])
			dfs(G[p][i], k);
}
int main(){
	scanf("%d %d", &n, &m);
	while(m--){
		scanf("%d %d", &a, &b);
		G[b].push_back(a);// 反向建边
	}
	for(int i = n;i >= 1;--i)
		if(!vis[i])
			dfs(i, i); //新的联通链,最大的编号就是初始编号
	for(int i = 1;i <= n;++i)
		printf("%d ", ans[i]);
	return 0;
}

题3:基础题:查找文献 洛谷5318

算法分析:

这就不分析了,跑一遍dfs和一遍bfs就完事了

AC代码:

#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;

const int maxn = 1e5 + 5;
vector<int> G[maxn];
int n, m, a, b, vis[maxn];
void dfs(int p){
	if(vis[p])	return ;
	vis[p] = 1;
	printf("%d ", p);
	for(int i = 0;i < (int)G[p].size();++i)
		if(!vis[G[p][i]])
			dfs(G[p][i]);
}

void bfs(int p){
	memset(vis, 0, sizeof(vis));
	queue<int> q;
	q.push(p);
	vis[p] = 1;
	while(!q.empty()){
		int Now = q.front(); q.pop();
		printf("%d ", Now);
		for(int i = 0;i < (int)G[Now].size();++i)
			if(!vis[G[Now][i]])
			{
				vis[G[Now][i]] = 1;
				q.push(G[Now][i]);
			}
	}
} 
int main(){
	scanf("%d %d", &n, &m);
	while(m--){
		scanf("%d %d", &a, &b);
		G[a].push_back(b);
	}
	for(int i = 1;i <= n;++i)
		sort(G[i].begin(), G[i].end());
	dfs(1);
	printf("\n");
	bfs(1);
	return 0;
}

题4:无序字母对 洛谷1341

算法分析:

这题太坑了!!!
这题可以这样抽象建模:
单词:边(字符就是端点)
结果字符串:就是一个路径
需要寻找每个单词都出现的字符串:找到每条边都走到的路径(欧拉路径)

欧拉路径的算法分析:

欧拉路本质就是一个dfs,存在的条件是:
条件1:入度为奇数的点有0个或2个(0个是欧拉回路)
条件2:它一定要是一个联通图(联通性用并查集或者dfs)

注意事项:倒着存储点。

坑点分析:

1:自环用dfs不怕

2:老是RE(这个真的有点玄学……)

AC代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn = 3005;
int m, vis[maxn], deg[maxn], G[maxn][maxn], isOK, s, cnt, ans[maxn];
char a, b; 
void dfs(int p){
	if(vis[p])	return ;
	vis[p] = 1;
	for(int i = 0;i < maxn;++i)
		if(!vis[i] && G[p][i])
			dfs(i);
}

void euler(int p){
	for(int i = 0;i < maxn;++i){
		if(G[p][i]){
			G[p][i]--;
			G[i][p]--;
			euler(i);
		}
	}
	ans[cnt++] = p;
	return ;
}

int main(){
	cin >> m;
	for(int i = 0;i < m;++i){
		cin >> a >> b;
		G[a - 'A'][b - 'A']++;
		G[b - 'A'][a - 'A']++;
		deg[a - 'A']++;
		deg[b - 'A']++;
	}
	if(1 == m){
		if(a > b)
			cout << b << a;
		else
			cout << a << b;
		return 0;
	}
	for(int i = 0;i < maxn;++i)
		if(deg[i] & 1)
		{
			isOK++;
			if(!s)
				s = i;
		}
	if(0 != isOK && 2 != isOK){
		dfs(s);
		for(int i = 0;i < maxn;++i)
			if(!vis[i] && deg[i])
			{
				isOK = 1;
				break;
			}
	}
	if(0 != isOK && 2 != isOK)
		cout << "No Solution";
	else{
		euler(s);
		for(int i = cnt - 1;i >= 0;--i)
			cout << char(ans[i] + 65);
	}
	return 0;
}
// IcCvXggwPpl
上一篇:SpringSecurity学习笔记


下一篇:树状数组&线段树