拓扑排序--P1983--UVA10305--讲解--Sabrina--Sabrinadol

传送门
题目描述
一条单向的铁路线上,依次有编号为 1, 2, …, n 1,2,…,n的 n n个火车站。每个火车站都有一个级别,最低为 11 级。现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次停靠了火车站 xx,则始发站、终点站之间所有级别大于等于火车站 xx 的都必须停靠。(注意:起始站和终点站自然也算作事先已知需要停靠的站点)

例如,下表是 5 5趟车次的运行情况。其中,前 44 趟车次均满足要求,而第 55 趟车次由于停靠了 33 号火车站(22 级)却未停靠途经的 66 号火车站(亦为 22 级)而不满足要求。

现有 mm 趟车次的运行情况(全部满足要求),试推算这 nn 个火车站至少分为几个不同的级别。

输入格式
第一行包含 22 个正整数 n, mn,m,用一个空格隔开。

第 i + 1i+1 行(1 ≤ i ≤ m)(1≤i≤m)中,首先是一个正整数 s_i(2 ≤ s_i ≤ n)s
i
​ (2≤s
i
​ ≤n),表示第 ii 趟车次有 s_is
i
​ 个停靠站;接下来有 s_is
i
​ 个正整数,表示所有停靠站的编号,从小到大排列。每两个数之间用一个空格隔开。输入保证所有的车次都满足要求。

输出格式
一个正整数,即 nn 个火车站最少划分的级别数。

输入输出样例
输入 #1 复制
9 2
4 1 3 5 6
3 3 5 6
输出 #1 复制
2
输入 #2 复制
9 3
4 1 3 5 6
3 3 5 6
3 1 5 9
输出 #2 复制
3
说明/提示
对于 20%20%的数据,1 ≤ n, m ≤ 101≤n,m≤10;

对于 50%50%的数据,1 ≤ n, m ≤ 1001≤n,m≤100;

对于 100%100%的数据,1 ≤ n, m ≤ 10001≤n,m≤1000。

希望各位看了这篇题解加知识讲解之后能够有队拓扑排序有基本的运用,我会尽力写得通俗易懂,这也是我一概的习惯

题解

0.写在前面

首先我们需要对拓扑排序有一个基本的认识
简单的来说,拓扑排序就是将所有的任务按照先后顺序进行排序,然后很方便地进行处理问题
下面是一道模板题(UVA10305):

题意翻译
John有n个任务要做,每个任务在做之前要先做特定的一些任务。
输入第一行包含两个整数n和m,其中1<=n<=100。 n表示任务数,而m表示有m条任务之间
的关系。 接下来有m行,每行包含两个整数i和j,表示任务i要在j之前做。
当读入两个0(i=0,j=0)时,输入结束。
输出包含q行,每行输出一条可行的安排方案。
输入 #1 复制
5 4
1 2
2 3
1 3
1 5
0 0
复制
输出 #1 复制
1 4 2 5 3
输出
1 4 2 5 3

#include <iostream>
#include <stdio.h>
#include <string.h>
 
using namespace std;
 
const int N = 100 + 1;
int g[N][N];
int degreein[N];
int ans[N];
int n, m;
 
void toposort()
{
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            if(g[i][j])
                degreein[j]++;//如果满足拓扑的性质那么,就让指向的那个点++;
 
    // 从节点号小的开始寻找
    for(int i = 1; i <= n; i++) {
        int k = 1;
        while(degreein[k] != 0)
            k++;//求对应的数
        ans[i] = k;
        degreein[k] = -1;
 
        // 去除已经找出的节点的入度
        for(int j = 1; j <= n; j++)
            if(g[k][j])
                degreein[j]--;
    }
}
 
int main()
{
    while(~scanf("%d%d", &n, &m) && (n || m)) {
        // 初始化
        memset(g, 0, sizeof(g));
        memset(degreein, 0, sizeof(degreein));
        memset(ans, 0, sizeof(ans));
 
        // 读入数据
        int u, v;
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &u, &v);
            g[u][v] = 1;
        }
 
        // 拓扑排序
        toposort();
 
        // 输出结果
        for(int i = 1; i < n; i++)
            printf("%d ", ans[i]);
        printf("%d\n", ans[n]);
    }
 
    return 0;
}

P1983

1.算法分析

(1)明显的使用topu排序,将输入数据与其补集进行连边,并且连边的时候,为了方便之后的遍历,顺便求出每个数的出度
(2)然后,在求解的时候,大致想法就是从出度为0的最低点开始扫,每次扫一层出度为0的点,ans++。
之后便将这一层的点与整个图分离,分到最后出度为0的点求不出来的时候,就结束循环

2.片段代码

1-(1)

	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		memset(book,0,sizeof(book));//用来标记是否停靠该点
		cin>>a;
		for(int j=1;j<=a;j++)
		{
			cin>>h[j];
			book[h[j]]=1;
		}
		for(int j=h[1];j<=h[a];j++)
		{
			if(!book[j])//如果没有停靠,就将它和停靠了的所有点连上一条有向边
			{
				for(int k=1;k<=a;k++)
				{
					if(!topu[j][h[k]])
					{
						topu[j][h[k]]=1;//指的是h[]指向j
						du[h[k]]++;//求h[]的出度
					}
				}
			}
		}
	}

2-(2)

	int top=0;

	do
	{
		top=0;
		for(int i=1;i<=n;i++)
		{
			if(!yoyo[i]&&du[i]==0)
			{
				top++;
				yoyo[i]=1;
				stack1[top]=i;//用来存储扫出的度为0的点
			}
		}	
		for(int i=1;i<=top;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(topu[stack1[i]][j])
				{
					topu[stack1[i]][j]=0;//去点去边
					du[j]--;
				}
			}
		}
		ans++;//这里要注意,因为最后一次多了一个循环,所以在输出时需要ans--;
	}while(top);

3.附上AC代码

#include<bits/stdc++.h>
#define maxn 10000
using namespace std;
int n,m;
int book[maxn];
int h[maxn];
int a;
int ans;
int yoyo[maxn],stack1[maxn];
int du[maxn];
int topu[1005][1005];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		memset(book,0,sizeof(book));
		cin>>a;
		for(int j=1;j<=a;j++)
		{
			cin>>h[j];
			book[h[j]]=1;
		}
		for(int j=h[1];j<=h[a];j++)
		{
			if(!book[j])
			{
				for(int k=1;k<=a;k++)
				{
					if(!topu[j][h[k]])
					{
						topu[j][h[k]]=1;
						du[h[k]]++;
					}
				}
			}
		}
	}
	int top=0;

	do
	{
		top=0;
		for(int i=1;i<=n;i++)
		{
			if(!yoyo[i]&&du[i]==0)
			{
				top++;
				yoyo[i]=1;
				stack1[top]=i;
			}
		}	
		for(int i=1;i<=top;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(topu[stack1[i]][j])
				{
					topu[stack1[i]][j]=0;
					du[j]--;
				}
			}
		}
		ans++;
	}while(top);
		
	cout<<ans-1<<endl;
	while1)
	cout<<"Sabrina"<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值