图的基本应用——洛谷题单详解

题目链接

一、查找文献

模板题,BFS,DFS,此题节点数较大,只能使用邻接表储存。又由于本题需要求字典序最小的序列,那么就要将邻接表存储的结点按从小到大进行排序。那么我们可以开一个二维vector数组来储存。

#include<iostream>
#include<cstdio>
#include<queue>
#include <vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXSIZE 100010
vector < int > G[MAXSIZE];
queue< int > q;//bfs的队列
bool v[MAXSIZE];//记录是否访问过
int n, m;
void dfs(int x, int num)//x为节点,num为次数
{
	v[x] = true;
	cout << x << " ";
	if (num == n)return;
	for (int i = 0; i < G[x].size(); i++)
	{
		if (!v[G[x][i]])
			dfs(G[x][i], num + 1);
	}
}
void bfs(int x)
{
	memset(v, false, sizeof(v));
	q.push(x);
	v[x] = true;
	while (!q.empty())
	{
		int f = q.front();
		q.pop();
		cout << f << " ";
		for (int i = 0; i < G[f].size(); i++)
		{
			if (!v[G[f][i]])
			{
				v[G[f][i]] = true;
				q.push(G[f][i]);
			}
		}
	}
}
int main()
{
	cin >> n >> m;
	int a,b;
	for (int i = 1; i <= m; i++)
	{
		cin >> a >> b;
		G[a].push_back(b);
	}
	for (int i = 1; i <= n; i++)
		sort(G[i].begin(), G[i].end());//对邻接表排序
	dfs(1, 0);
	cout << endl;
	bfs(1);
	cout << endl;
}

二、图的遍历

按题目来每次考虑每个点可以到达点编号最大的点,不如考虑较大的点可以反向到达哪些点。循环从N到1,则每个点i能访问到的结点的A值都是i

#include<iostream>
#include<cstdio>
#include <vector>
using namespace std;
#define MAXSIZE 100010
vector < int > G[MAXSIZE];
int v[MAXSIZE];//记录是否访问过
int n, m;
void dfs(int x,int d)//x为节点,d为编号最大的点
{
	if (v[x])return;
	v[x] = d;
	for (int i = 0; i < G[x].size(); i++)
	{
		if (!v[G[x][i]])
			dfs(G[x][i],d);
	}
}
int main()
{
	cin >> n >> m;
	int a,b;
	for (int i = 1; i <= m; i++)
	{
		cin >> a >> b;
		G[b].push_back(a);
	}
	for (int i = n; i >= 1; i--)
		dfs(i, i);
	dfs(1, 0);
	for (int i = 1; i <= n; i++)
		cout << v[i] << " ";
	cout << endl;
}

三、P1113 杂务

一道拓扑排序题,
初始化队列,将入度为 0 的节点放入队列。
取出队首,遍历其出边,将能够到达的点入度减1,同时维护答案数组。
若在此时一个点的入度变为0,那么将其加入队列。
回到第二步,直到队列为空。

#include<iostream>
#include<cstdio>
#include<queue>
#include <vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXSIZE 10005
vector < int > g[MAXSIZE];//邻接表法
int l[MAXSIZE];//长度
int r[MAXSIZE];//入度
queue < int > q;//拓扑排序队列
int t[MAXSIZE];//各个节点最早结束时间
int main() 
{
	int n;
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		int m,len,a;
		cin >> m>>len;
		l[m] = len;
		while (cin >> a)
		{
			if (!a) break;
			g[a].push_back(m);
			r[m]++;
		}
	}
	for (int i = 1; i <= n; i++)
	{
		if (r[i] == 0)
			q.push(i);
	}
	t[1] = l[1];//第一个节点一定最小,且结束时间为持续时间
	while (!q.empty())
	{
		int front = q.front();
		q.pop();
		for (int i = 0; i < g[front].size(); i++)
		{
			r[g[front][i]]--;
			if (r[g[front][i]] == 0)
			{
				q.push(g[front][i]);
			}
			t[g[front][i]] = max(t[g[front][i]], t[front] + l[g[front][i]]);
		}
	}

	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		ans = max(ans, t[i]);
	}
	cout << ans << endl;
	return 0;
}

四、P4017 最大食物链计数

想要找到一条 最大食物链 ,则起始点入度要为0,终点出度要为0。于是有,既要记录入度,还要记录出度。

以第 i 个点结束的最大食物链的 数量 = 以指向第 i 个点的点结尾的最大食物链的 数量的和

由此呼之欲出,拓扑排序。最后将出度为0的点的食物链数量相加即可

#include<iostream>
#include<cstdio>
#include<queue>
#include <vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXSIZE 10005
#define mod 80112002
vector < int > g[MAXSIZE];//邻接表法
int in[MAXSIZE];//入度
int out[MAXSIZE];//出度
int num[MAXSIZE];//路径数量
queue < int > q;//拓扑排序队列
int main() 
{
	long int n, m,x,y;
	cin >> n >> m;
	for (long int i = 0; i < m; i++)
	{
		cin >> x >> y;
		g[x].push_back(y);
		out[x]++;
		in[y]++;
	}
	for (int i = 1; i <= n; i++)
	{
		if (in[i] == 0)
		{
			q.push(i);
			num[i] = 1;//令到其的路径数量为1
		}
	}
	while (!q.empty())
	{
		int front = q.front();
		q.pop();
		for (int i = 0; i < g[front].size(); i++)
		{
			in[g[front][i]]--;
			if (in[g[front][i]] == 0)
				q.push(g[front][i]);
			num[g[front][i]] = (num[front] + num[g[front][i]]) % mod;
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		if (out[i] == 0)
		{
			ans = (ans + num[i]) % mod;
		}
	}
	cout << ans << endl;
}


五、P1807 最长路

对于一个有向无环图,可以采取广搜将结点依次搜索,记录当前结点的最长路并在循环过程中不断更新。

#include<iostream>
#include<cstdio>
#include<queue>
#include <vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXSIZE 10005
int max_length[MAXSIZE];//最长路径
queue < int > q;
int w[MAXSIZE][MAXSIZE];
int main() 
{
	int n, m,x,y,v;
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		cin >> x >> y>>v;
		w[x][y] =max(w[x][y],v);
	}
	memset(max_length, -1, sizeof(max_length));
	max_length[1] = 0;
	q.push(1);
	while (!q.empty())
	{
		int front = q.front();
		q.pop();
		for (int i = 1; i <= n; i++)
		{
			if (w[front][i] && (w[front][i] + max_length[front] > max_length[i]))
			{
				max_length[i] = w[front][i] + max_length[front];
				q.push(i);
			}

		}
	}
	cout << max_length[n] << endl;
}

六、P1127 词链

题目大意:如果单词 X 的末字母与单词 YY的首字母相同,则 X 与 Y 可以相连成 X.Y。现在给你一些单词,请你找到字典序最小的词链,使得这些单词在词链中出现且仅出现一次。

这里是引用

#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
#define MAXSIZE 1005
string s[MAXSIZE];
map < char, int > s1, s2;//统计头尾
int flag=0;
int vis[MAXSIZE];//用来判断当前的字符串是否已经用过
string ans[MAXSIZE];//搜索时更新的数组
string now[MAXSIZE];//答案数组
int sum = 1;
int n;
void dfs(int x, int y)//x为要搜索的字符串下标,x为此时搜索次数
{
	if (flag == 1)
		return;
	if (y == n)
	{
		flag = 1;
		for (int i = 1; i <= sum; i++)
			now[i] = ans[i];
		return;
		return;
	}
	for (int i = 1; i <= n; i++)
	{
		if (!vis[i])
		{
			if (s[x][s[x].length() - 1] == s[i][0])
			{
				ans[++sum] = s[i];
				vis[i] = 1;
				dfs(i, y + 1);
				sum--;
				vis[i] = 0;
			}
		}
	}

}
int main() 
{
	memset(vis, 0, sizeof(vis));
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> s[i];
		s1[s[i][0]]++;
		s2[s[i][s[i].length() - 1]]++;//尾部
	}
	int start = 1;
	sort(s + 1, s + 1 + n);//字典序
	for (int i = 1; i <= n; i++)
	{
		if (s1[s[i][0]] - s2[s[i][0]]==1)//当前字符串第一个字母前后出现差值为1
		{
			start = i;
			break;
		}
	}
	vis[start] = 1;
	ans[sum] = s[start];
	dfs(start, 1);
	if (flag == 0)
		cout << "***" << endl;
	else
	{
		for (int i = 1; i <=n; i++)
		{
			if (i!=1)
				cout << ".";
			cout << now[i];
		}
	}
}


七、P2853 [USACO06DEC]Cow Picnic S

从k个奶牛分别dfs,用num[i]表示第i个牧场被遍历过多少次,最后只有num[i]==k的牧场满足条件。无权的有向图也可以用vector存储。

#include<iostream>
#include<cstdio>
#include <vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXSIZE 10005
int num[MAXSIZE];//保存次数
vector < int > g[MAXSIZE];
int a[MAXSIZE];
int vis[MAXSIZE];
void dfs(int x)
{
	vis[x] = 1;
	num[x]++;
	for (int i = 0; i < g[x].size(); i++)
	{
		if (!vis[g[x][i]])
		{
			dfs(g[x][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;
		g[x].push_back(y);
	}
	for (int i = 1; i <= k; i++)
	{
		memset(vis, 0, sizeof(vis));
		dfs(a[i]);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		if (num[i] == k)
		{
			ans++;
		}
	}
	cout << ans << endl;
}

八、P1363 幻象迷宫

某一个点可以走到它在别的图的映射的它自己就行(即是映射地址一样,但真实地址不一样),但走到它自己图上的它自己就不行。重点就在于此。

#include<iostream>
#include<cstring>
using namespace std;
#define MAXSIZE 1505
const int dx[5] = { 0,-1,0,1,0 };
const int dy[5] = { 0,0,1,0,-1 };
char g[MAXSIZE][MAXSIZE];
int used[MAXSIZE][MAXSIZE][3];
//0记录是否来过,1记录上一次到达时真实x坐标,2记录真实y坐标
int flag=0;
int n, m;
void dfs(int x, int y, int nx, int ny)//x,y为映射坐标,nx,ny为真实坐标
{
	//某一个点可以走到它在别的图的它自己就行,就代表当记录的真实坐标两个不一致,则认为走出
	if (used[x][y][0] == 1 && (used[x][y][1] != nx || used[x][y][2] != ny))
		flag = 1;
	if (used[x][y][0])
		return;
	//走过之后在记录
	if (flag)
		return;
	used[x][y][0] = 1;
	used[x][y][1] = nx ;
	used[x][y][2] = ny ;
	for (int i = 1; i <= 4; i++)
	{
		int tox, toy;
		tox = (x + dx[i] + n) % n;
		toy = (y + dy[i] + m) % m;
		if (g[tox][toy] != '#')
		{
			dfs(tox, toy, nx + dx[i], ny + dy[i]);
		}
	}
}
void work()
{
	flag = 0;
	int sx=0, sy=0;//记录开始
	memset(g, 0, sizeof(g));
	memset(used, 0, sizeof(used));
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			cin >> g[i][j];
			if (g[i][j] == 'S')
			{
				sx = i;
				sy = j;
			}
		}
	}
	dfs(sx, sy, sx, sy);
	if (flag)
		cout << "Yes" << endl;
	else
		cout << "No" << endl;
}
int main()
{
	while (cin >> n >> m)
		work();
}

九、排序

输出要求输出到第几次就行了,或者不行了,就说明我们每建一条边就需要一次拓扑排序。
有三种情况,1:有稳定的拓扑排序,2:有环,3:无稳定拓扑排序
有稳定拓扑排序说明拓扑排序的层数是n,看最大层数ans是否等于n。
如果拓扑排序没能遍历所有的点,就说明存在一个环。也就是下面的sum!=char_mumber.size()。char_mumber是用来存储目前元素(点)个数的。如果前两种都不是,那肯定就是最后一种了

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#include <set>
#include <bits/stdc++.h>
using namespace std;
#define MAXSIZE 28
vector < int > g[MAXSIZE];
int in[MAXSIZE];//入度保存
int ans=0;//层数保存
int n,m;
set < int > char_number;
int sum = 0;//参与拓扑排序的字母数量
int k;//此时的位置
void make_in();
struct node
{
	int val;//层数
	int spot;//点
	node(int spot = 0, int val = 0) :spot(spot), val(val) {}
	//结构体的重构
};
void make()
{
	make_in();
	queue< int > q;
	for (int i = 0; i < 26; i++)
	{
		if (in[i] == 0 && char_number.count(i))
		{
			q.push(i);
			cout << char(i + 'A');
		}
	}
	while (!q.empty())
	{
		int front = q.front();
		q.pop();
		for (int i = 0; i < g[front].size(); i++)
		{
			in[g[front][i]]--;
			if (in[g[front][i]]== 0)
			{
				q.push(g[front][i]);
				cout << char(g[front][i] + 'A');
			}
		}
	}
}
void test()
{
	make_in();
	queue< node > q;
	for (int i = 0; i < 26; i++)
	{
		if (in[i] == 0 && char_number.count(i))
		{
			q.push(node(i,1));
			sum++;
		}
	}
	while (!q.empty())
	{
		int front = q.front().spot;
		int v = q.front().val;
		q.pop();
		for (int i = 0; i < g[front].size(); i++)
		{
			in[g[front][i]]--;
			if (in[g[front][i]]==0)
			{
				sum++;
				q.push(node(g[front][i], v + 1));
				ans = max(ans, v + 1);
			}
		}
	}
	if (ans == n)
	{
		cout << "Sorted sequence determined after " <<k<< " relations: ";
		make();
		cout << ".";
		exit(0);
	}
	if (sum != char_number.size())
	{
		cout << "Inconsistency found after "<< k<<" relations." ;
		exit(0);
	}
}
void make_in()//计算入度
{
	memset(in, 0, sizeof(in));
	for (int i = 0; i < 26; i++)
	{
		for (int j = 0; j < g[i].size(); j++)
		{
			in[g[i][j]]++;
		}
	}
}
int main()
{
	cin >> n >> m;
	string s;
	for (int i = 1; i <=m; i++)
	{
		cin >> s;
		g[s[0] - 'A'].push_back(s[2] - 'A');
		in[s[0] - 'A']++;
		char_number.insert(s[0] - 'A');
		char_number.insert(s[2] - 'A');
		//每次要清楚ans,sum计数和用k标记此时的位置
		ans = 0;
		sum=0;
		k = i;
		test();
	}
	cout << "Sorted sequence cannot be determined.";
}

十、P1983 车站分级

因为火车都要停靠比它高级(大于等于)的车站,所以其它不停的就是比它级别小(小于)的车站,现在求最高等级的车站是几级。在所有不停的站的级别小于停靠的站的情况下,我们可以做出一张图。最后利用拓扑的思想,一层一层的删点删线,统计下分了多少层

#include<iostream>
#include<cstring>
#include<vector>
#include <set>
using namespace std;
#define maxsize 1505
int n, m,k,a[maxsize];
bool v[maxsize];//记录路上的车站是否停过
int g[maxsize][maxsize];
int in[maxsize];
bool vis[maxsize];//记录被删的点
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		cin >> k;
		memset(v, 0, sizeof(v));
		for (int j = 1; j <= k; j++) 
		{
			cin >> a[j];
			v[a[j]] = true;
		}
		for (int j = a[1]; j <= a[k]; j++)
		{
			if (!v[j])
			{//以此加边
				for (int s = 1; s <= k; s++)
				{
					if (!g[j][a[s]])
					{
						g[j][a[s]] = 1;
						in[a[s]]++;//此点入度加一,表示从小到大
					}
				}
			}
		}
	}
	int ans = 0,top;
	int stack[maxsize];
	do//统计多少层
	{
		 top = 0;
		 for (int i = 1; i <= n; i++)
		 {
			 if (in[i] == 0 && !vis[i])
			 {
				 stack[++top] = i;
				 vis[i] = true;
			 }
		 }
		 for (int i = 1; i <= top; i++)
		 {
			 for (int j = 1; j <= n; j++)
			 {
				 if (g[stack[i]][j] == 1)
				 {
					 in[j]--;
					 g[stack[i]][j] = 0;
				 }
			 }
		 }
		 ans++;
	} while (top);
	cout << ans-1 << endl;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值