图论---拓扑排序的应用

    最近研究了几道图论的题目,都是图论入门的算法,用的比较多的就是拓扑排序,多用于有着先后顺序的题目,也可以用来判断环,做个小总结。

杂物

题目链接:杂务 - 洛谷 

    这一题需要计算最短的时间,利用了记忆化搜索的方式。将每一项工作抽象为图中的节点,所有前面需要完成的也就是节点的前驱,因为每一项工作完成所需要的最短总时长其实就是完成前面需要的工作的最早时长再加上这个工作的时长,因为所有工作的最早完成时间在一开始是未知的,所以需要先把所有前驱给计算出来, 所以搜索是需要先更新前驱结点,如果计算过了就直接返回结果,本质上也是拓扑排序,利用转移方程finalT[i]=min(finalT[...])+t[i],也就是当前工作的时长加上最早前驱计算每个节点,确定最终答案。

#include
#include
#include
#define Max 100100
using namespace std;
vectorv1[Max];
int t[Max];//每个点的时间
int flag[Max];//标记每个点的访问情况
int finalT[Max];//标记最终的答案
int n;

int dfs(int x)
{
	if (flag[x])return finalT[x];
	for (int i = 0; i < v1[x].size(); i++)
		finalT[x] = max(finalT[x], finalT[v1[x][i]] + t[x]);
	flag[x] = 1;
	return finalT[x];
}

int main()
{
	scanf("%d", &n);
	int x, y;
	for (int i = 1; i <= n; i++)
	{
		scanf("%d%d", &x, t + i);
		while ( scanf("%d",&y) && y)//这样写更简洁
		{
			v1[x].push_back(y);//这个方笛一定要注意,给的形式是前驱
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
		finalT[i] = t[i];
	for (int i = 1; i <= n; i++)
		ans = max(ans, dfs(i));
	printf("%d\n", ans);
	return 0;
}

 最大食物链计数

题目链接:最大食物链计数 - 洛谷 

    这一题需要计算食物链的总数,将每个生物抽象为图中节点,一个生物吃另一个生物就是作为前驱。那么只有一个节点只有入度而没有出度才可以算作一条完整的食物链,对于每个节点,到达这个节点所有的食物链总数其实就是所有指向这个节点的食物链总和,也就是num[i]=∑num[j],j是i的前驱, 初始状态是将每个只有出度的点作为1,最后求和是对所有没有出度的求和。

#include
#include
#include
using namespace std;
#define ll long long
#define mod 80112002
vectorv[10000];
int n,m;
int flag[10000];
int vis[10000];//标记这个点有没有出度

int cal(int x)
{
	if (flag[x])return flag[x];
	if (v[x].size() == 0)
		return flag[x] = 1;//没有前驱就是1
	for (int i = 0; i < v[x].size(); i++)
	{
		flag[x] = (flag[x] + cal(v[x][i])) % mod;
	}
	return flag[x];
}

int main()
{
	scanf("%d%d", &n, &m);
	int x, y;
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &x, &y);
		vis[x] = 1;
		v[y].push_back(x);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		if(!vis[i])
			ans = (ans + cal(i)) % mod;//cal计算所有前驱的和
	}
	printf("%d\n", ans);
	return 0;
}

排序

题目链接:排序 - 洛谷

    这道题因为有三种情况,也就是矛盾---出现环,无法排序---没有唯一拓扑序列,排序完成--有唯一的拓扑序列,恰好考察了拓扑排序的应用。这一题读入点要标记出现的点,在排序时候只要出现的点都有了那么排序就完成了,但是在排序中会出现几种情况:第一种是找了一轮都找不到度为0的点,那么这时候是出现矛盾了;第二种要是找了一轮找到了好几个度为0的那么就无法确定,一开始我是吃了这里的亏,也就是在函数中出现无法确定直接返回,但是由于无法确定还可能最终是矛盾的,所以不可以直接返回,最终返回是矛盾优先,因为直接就不用后面的判断了;第三种就是排序完成了,出现的字母个数也是等于总个数,那么就可以退出了。

#include
#include
#include
#include
using namespace std;
vectorv[200];
int du[200],du2[200];
char res[30];
bool exist[200];
bool flag[200];
queueq;
int n, m;

int topo_sort(int cag)
{
	int cnt = 0;
	int not_sure = 0;
	for (int i = 'A'; i <= 'Z'; i++)
	{
		int first = 0;//标记是不是只有一个入队
		if (exist[i] && du2[i] == 0)
		{
			if (!first)first = 1;
			else not_sure = 1;
			q.push(i);
			res[++cnt] = i;
		}
	}
	if (q.empty())return -1;//矛盾
	while (!q.empty())
	{
		int x = q.front();
		q.pop();
		int first = 0;
		for (int i = 0; i < v[x].size(); i++)
		{
			du2[v[x][i]]--;
			if (!du2[v[x][i]])
			{
				if (!first)first = 1;
				else not_sure = 1;
				q.push(v[x][i]);
				res[++cnt] = v[x][i];
			}
		}
	}
	//printf("cnt=%d,cag=%d\n", cnt,cag);
	if (cnt != cag)return -1;//如果没排序完,那么就是有环,也就是矛盾
	if (not_sure)return 0;//不确定
	return 1;//排序完毕
}

int main()
{
	scanf("%d%d", &n, &m);
	char x, y;
	int cag = 0;//当前有的种类
	for (int i = 1; i <= m; i++)
	{
		scanf(" %c<%c", &x, &y);
		cag += (1-exist[x] + 1-exist[y]);
		exist[x] = 1;
		exist[y] = 1;
		//printf("%c:%c\n", x, y);
		v[x].push_back(y);
		du[y]++;
		for (int j = 'A'; j <= 'Z'; j++)
			du2[j] = du[j];
		int ans = topo_sort(cag);
		//printf("%d %d\n", i, ans);
		if (ans == -1)//矛盾
		{
			printf("Inconsistency found after %d relations.\n", i);
			return 0;
		}
		else if (cag==n && ans == 1)//排序完了
		{
			printf("Sorted sequence determined after %d relations: ", i, res);
			for (int j = 1; j <= n; j++) printf("%c", res[j]);
			printf(".\n");
			return 0;
		}
	}
	printf("Sorted sequence cannot be determined.\n");
	return 0;
}

车站分级

题目链接:[NOIP2013 普及组] 车站分级 - 洛谷

    这一题放在这里就是因为我觉得这一题就比较像是上一题排序的变形,从题目给出条件来看,车站经过的所有车站只要没有停,那么这个车站一定比停过的车站等级都要低,所以给出一个序列之后,比如1,3,设dj[i]为车站i的等级,那么就隐含了dj[1]>dj[2],dj[3]>dj[2],那么把所有的序列都转化为不等式,那么这一题就跟上一题一样了。需要注意的就是这一题需要统计的数量就是有多少层,每次找完一轮,度为0的都算作一层,相当于这些车站之间没有大小关系,但是跟不同层的是有前后关系的。

#include
#include
#include
#include
using namespace std;
#define N 1001
bool flag[N][N];//标记这条边flag[x][y]x是否已经建立和y的关系
vectorv[N];
int n, m,ans;
bool vis[N];
int du[N];
int Min = 0x3f3f3f, Max = -0x3f3f3f;
int a[N];//存储车道序列和后面暂时存放度0的点
void topo()
{
	//要计算有多少层,每一次找完一轮有几个度为0,那么这一次找到的都是算作一层
	//先存储起来,然后逐个减度
	int all_num=Max-Min+1;//这一轮找到的个数和总个数
	while (all_num)
	{
		int num = 0;
		//for (int i = Min; i <= Max; i++)
		//	printf("du[i]=%d\n", du[i]);
		for (int i = Min; i <= Max; i++)
		{
			//printf("ans=%d\n", ans);
			if (!vis[i] && !du[i])
			{
				a[++num] = i;
				all_num--;
				vis[i] = 1;
			}
		}
		ans++;//一层
		//printf("all_num=%d\n", all_num);
		for (int i = 1; i <= num; i++)
		{
			int x = a[i];
			for (int j = 0; j < v[x].size(); j++)
				du[v[x][j]]--;
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	int t, x;
	for (int i = 1; i <= m; i++)
	{
		scanf("%d", &t);
		for (int i = 1; i <= t; i++)
		{
			scanf("%d", a+i);
			vis[a[i]] = 1;
		}
		//printf("t=%d\n", t);
		Min = min(Min, a[1]);
		Max = max(Max, a[t]);
		for (int i = a[1]; i <= a[t]; i++)
		{
			if (!vis[i])//表示没有出现过,这个点要比所有出现过的点要小
			{
				//建边
				for (int j = 1; j <= t; j++)
				{
					if (!flag[i][a[j]])//如果没有建过边
					{
						flag[i][a[j]] = 1;
						v[i].push_back(a[j]);
						du[a[j]]++;
					}
				}
			}
		}
		//一条车道建立完毕
		for (int i = a[1]; i <= a[t]; i++)
			vis[i] = 0;
	}
	//printf("%d %d\n", Min, Max);
	topo();
	printf("%d\n", ans);
	return 0;
}

 就先写到这么多吧。

你可能感兴趣的:(图论,算法,数据结构)