图论拓扑排序题目题解

拓扑排序

定义:有一个有向无环图,每一条边都代表一个先后关系,从a到b的边就代表b比a先做,拓扑排序就是找到一个顺序满足所有的先后关系。

写法:每次在统计的数组或者队列中加入当前入度为零的点,每次加入一个点就把从这个点出发的所有路径都删除,直到队列或者数组为空,而且需要记录每一个点的入度和记录这个点的数据,最后通过记录的数据来回答题目要求的东西。

最长路径

题目:

给你n个点和m条有向边,保证没有环,求最长路径的长度。

题目条件:

1.\(n<=200000,m<=300000。\)

分析:

这道题就是经典的拓扑排序题目,每次都需要更新初始点到这个点的最长路径,最后输出记录数值的最大值。

程序:

c++ 复制代码
#include<bits/stdc++.h>
using namespace std;
queue<int> q;
vector<int> a[200001];
int n,m,b[200001],c[200001],ans;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		b[y]++;
		a[x].push_back(y);
	}
	for(int i=1;i<=n;i++)
		if(b[i]==0)
			q.push(i);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(auto i:a[x])
		{
			b[i]--;
			c[i]=max(c[i],c[x]+1);
			if(!b[i])
				q.push(i);
		}
	}
	for(int i=1;i<=n;i++)
		ans=max(ans,c[i]);
	cout<<ans<<'\n';
	return 0;
}

2.奖金

题目:

由于无敌的凡凡在世界英俊帅气男总决选中胜出,Yali Company总经理Mr.Z心情好,决定给每位员工发奖金。公司决定以每个人本年在公司的贡献为标准来计算他们得到奖金的多少。于是Mr.Z下令召开m方会谈。每位参加会谈的代表提出了自己的意见:"我认为员工a的奖金应该比b高!"Mr.Z决定要找出一种奖金方案,满足各位代表的意见,且同时使得总奖金数最少。每位员工奖金最少为100元。

题目条件:

1.\(n<=10000,m<=20000。\)

2.无法找到合理方案,则输出"Poor Xed"。

分析:

这里也很容易想到是找一个顺序,最开始是100,后面依次加一,因此我们要针对原来给出的边反向建图,a到b的边代表b比a的奖金高,还是记录入度,接着就是普通的拓扑排序,最后把每个点求得的奖金加起来,如果有没走到的,也就是数值为0,输出题目要求的东西。

程序:

c++ 复制代码
#include<bits/stdc++.h>
using namespace std;
queue<int> q;
vector<int> a[10001];
int n,m,b[10001],c[10001],k=100,ans;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		b[x]++;
		a[y].push_back(x);
	}
	for(int i=1;i<=n;i++)
		if(b[i]==0)
		{
			q.push(i);
			c[i]=100;
		}
	while(!q.empty())
	{
		k++;
		int x=q.front();
		q.pop();
		for(auto i:a[x])
		{
			b[i]--;
			c[i]=max(c[i],c[x]+1);
			if(!b[i])
				q.push(i);
		}
	}
	for(int i=1;i<=n;i++)
	{
		ans+=c[i];
		if(!c[i])
		{
			cout<<"Poor Xed"<<'\n';
			return 0;
		}
	}
	cout<<ans<<'\n';
	return 0;
}

3.复杂的按钮

题目:

小K在遗迹探险时遇到了N个按钮,刚开始所有的按钮都处于开状态,小K的经验告诉他把所有的按钮都关上会有"好事"发生,可是有些按钮按下时会让其他一些已经闭合的按钮弹开。经过研究,每个按钮都对应着一个固定的弹开集合,这个按钮按下时,弹开集合中所有的按钮都会变为开状态。现在小K想知道是否能让所有的按钮变为闭合状态。如果能,打印最少步数以及方案,否则,打印"no solution"。输出字典序最小的方案。

题目条件:

1.\(1≤N≤30000。\)

2.每一个点对应的弹开集合中的元素个数相加小于等于1000000。

分析:

这道题目需要字典序最小,也就是尽量加入最小值,那么建图就是点i到弹开集合中的每个点,我们可以用stl里的set来统计,每次在答案中加入一个set中的最小值,别的都跟普通的拓扑排序一样。

程序:

c++ 复制代码
#include<bits/stdc++.h>
using namespace std;
set<int> s;
queue<int> q;
int n,b[30001];
vector<int> a[30001],ans;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int k;
		cin>>k;
		while(k--)
		{
			int x;
			cin>>x;
			b[x]++;
			a[i].push_back(x);
		}
	}
	for(int i=1;i<=n;i++)
		if(!b[i])
			s.insert(i);
	while(!s.empty())
	{
		int x=*s.begin();
		s.erase(s.begin());
		ans.push_back(x);
		for(auto i:a[x])
			if(--b[i]==0)
				s.insert(i);
	}
	if(ans.size()<n)
		cout<<"no solution"<<'\n';
	else
	{
		cout<<n<<'\n';
		for(auto i:ans)
			cout<<i<<" ";
	}
	return 0;
}