NOIP2013普及组T4

文章详细解释了如何根据给定的输入格式,通过递推式和图论方法解决铁路线车站等级划分问题,给出了输入样例和输出样例,以及对应的代码实现部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

题目概述

题目描述

输入格式

输出格式 

输入样例 1

输出样例1

输入样例2

 输出样例2

解题过程

1. 理解题目

2.提意转换

1)整体转局部

2)局部转图论

3)递推式初步形成

4)进一步分析

3.代码实现


题目概述

题目描述

一条单向的铁路线上,依次有编号为 1, 2, 3……, n 的  n  个火车站。每个火车站都有一个级别,最低为  1 级。现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次停靠了火车站 x ,则始发站、终点站之间所有级别大于等于火车站 x 的都必须停靠。(注意:起始站和终点站自然也算作事先已知需要停靠的站点) 例如,下表是 5 趟车次的运行情况。其中,前 4 趟车次均满足要求,而第 5 趟车次由于停靠了 3 号火车站(2 级)却未停靠途经的 6 号火车站(亦为 2 级)而不满足要求。 现有 m 趟车次的运行情况(全部满足要求),试推算这 n个火车站至少分为几个不同的级别。

输入格式

第一行包含 2 个正整数  n,m 用一个空格隔开。

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

输出格式 

输出文件 level.out 输出只有一行,包含一个正整数,即  n 个火车站最少划分的级别数。

输入样例 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


解题过程

1. 理解题目

这道题的题意本来就比较抽象,不过你可以这么理解,每个火车如果停了小城市,就必须停靠大城市。例如:

假设有一条火车从上海到四川,因为停靠过中城市和大城市(起点和终点),所以南京这个大城市也一定要停靠。但是,如果我停靠了苏州但是不停靠南京,就不符合常理。而这里的城市的大小就是题目中的车站等级。

2.提意转换 (重点)

1)整体转局部

整个题目其实最难处理的部分的部分就是: “如果这趟车次停靠了火车站 x ,则始发站、终点站之间所有级别大于等于火车站 x 的都必须停靠。”

这显然不适合我们 DP。于是,我们转化为:停靠的站点的每一个级别一定要大于不停靠的每一个级别(注意:是大于,不是大于等于),即L_1 > L_2

那这有什么区别吗?一个是整体的一个规则,而另一个却是局部的,适合处理。

2)局部转图论 (难点)

我们可以把每一个站点视为图上的一个点,然后在把每一个L_1 > L_2视为一条 L_1 \rightarrow L_2 的线,这样,我们便形成了一个有向图。题中第一趟火车的有向图应该长这样:

而现在,我要使得每一个箭头都成立,那不就是拓扑排序的要求吗?

BUT, L_i 允许并列,所以我们把这样的并列称为拓扑独立集,即两两互不可达

那为什叫 “独立” 集呢?如果两个一样的数,要满足上述要求,是不肯能有边的。所以它们互不可达。

3)递推式初步形成

那么,要满足这个规则,且要让车站等级最小,我们很快便能得到递推式:

L_x = max_{x \to y}^{}L_y + 1

我们再去递推,然后就会得到这样一张图。

那计算顺序呢?明显是拓扑排序的逆序。

那这里要涉及到拓扑排序的内容,比较复杂。

4)进一步分析

这里我们要邀请一位嘉宾进入我们的题目

这个定理说:最短子序列覆盖 = 最长反链。至于这个定理的证明,有点复杂,请点击。

而题目的问题其实是以上的图的覆盖问题,所以我们可以把它转化为再这一个图上最长能走多远

那么这个问题可以用 DP 就可以完美实现。

3. 代码实现


完整代码

#include<bits/stdc++.h>
using namespace std;
int N = 1005;
int main () {
	int n, m, s;
	int z [N], l [N] [N], du [N], f [N], qi;
	bool vis [N];
	queue <int> q;
	cin >> n >> m;
	for (int i = 1; i <= m; i ++) {
		cin >> s;
		memset (vis, 0, sizeof (vis));
		for (int j = 1; j <= s; j ++){
			cin >> z [j];
			vis [z [j]] = true;  
		}
		for (int j = z [1] + 1; j <= z [s];j ++) {
			if (vis [j] == false) {
				for (int k = 1; k <= s; k ++) {
					if (l [j] [z [k]] == 0){
						l [j] [z[k]] = 1;
						du [z [k]] += 1;
					}
				}
			}
		}  
	}
	for (int i = 0; i <= n - 1; i ++){
		if (du [i] == 0) {
			q.push (i);
			f [i] = 1; 
		}
	}
	while (!q.empty ()) {
		qi = q.front ();
		q.pop ();
		for (int i = 0; i <= n - 1; i ++) {
			if (l [qi] [i] == 1) {
				f [i] = max (f [qi] + 1, f [i]);
				l [qi] [i] = 0;
				du [i] -= 1;
				if (du [i] == 0) q.push(i);
			}
		}	
	}
	sort (f, f + n);
	cout << f [n - 1];
	return 0;
} 

<think>嗯,我现在得仔细看看用户提供的代码,找出他在NOIP2013普及组T4这道中的错误。这道应该是一个拓扑排序相关的目,可能涉及到求最长路径或者层级的问,因为目中的ans记录的是最大的层级数,也就是可能需要确定每个节点所在的层,然后找到最大的那个。 首先,用户代码的大致结构是这样的:输入n和m,然后处理m个测试用例。每个用例输入t个点,标记这些点为vis[x]=1,然后对于每个tt[i]中的点,遍历所有未被标记的点j,建立从j到tt[i]的边,同时增加tt[i]的入度。之后进行拓扑排序,计算每个节点的层级,最后输出最大的层级ans。 可能的错误点在哪里呢?我得仔细分析每一步的逻辑是否正确。 首先,目中的问可能类似于“车站划分”的问,每个测试用例可能代表一个车次的站点顺序。比如,每个车次给出t个站点,这些站点必须按顺序到达,也就是说,前面的站点必须比后面的站点层级低。例如,如果一个车次有站点序列a1,a2,a3,那么需要建立a1到a2的边,a2到a3的边,以及所有非该车次的站点到这些站点的边?或者可能用户的理解有误? 但用户代码的处理方式是,对于每个车次的t个站点tt[1..t],然后对于每个tt[i],将所有的未被vis标记的点j(即不在这个车次中的站点)都向tt[i]建立边。这似乎有问。比如,假设车次中的站点是按顺序的,比如S1,S2,S3,那么正确的依赖应该是S1必须在S2之前,S2在S3之前。所以应该建立S1→S2,S2→S3的边,而不是让所有不在车次中的点都指向S1、S2、S3。这可能就是用户代码的错误所在。 比如,正确的处理应该是,对于每个车次中的连续站点,比如tt[1], tt[2], ..., tt[t],那么对于每个i从1到t-1,需要建立tt[i]到tt[i+1]的边,这样就能保证tt[i]的层级比tt[i+1]低。而用户当前的代码则是,对于每个tt[i],所有不在该车次的站点j都向tt[i]建立边,这显然不符合目的要求。这会导致每个车次中的站点被所有不在该车次的站点所指向,这显然不正确。例如,车次中的各个站点之间本应有顺序关系,但用户代码并没有建立这些顺序,反而建立了大量无关的边。 所以,这应该是用户代码的主要错误。比如,在样例输入中,比如当车次是1 3 5 6时,正确的做法是建立1→3,3→5,5→6的边,而用户的代码却将所有不在车次的节点(比如2、4等)向这三个节点建立边,这明显是错误的。这会使得每个车次内的节点之间没有边,反而其他节点指向他们,导致拓扑排序的结果不正确。 所以,用户的问出在处理每个车次的逻辑上。正确的做法是,对于每个车次中的连续站点,建立从前到后的边。而用户的代码错误地将所有不在车次的节点连接到车次中的每个节点,导致车次内部的顺序没有被正确处理,从而层级计算错误。 另外,可能还有其他问。比如,在处理每个车次时,用户使用了两层循环:i从1到t,然后j从1到n,如果j不在当前车次中,就建立j到tt[i]的边。这会导致每个车次中的每个tt[i]都被所有不在车次的节点j所指向。这显然不符合目的要求。比如,假设车次中的节点是按顺序的,那么正确的依赖应该是车次中的前驱节点指向后续节点,而不是其他节点都指向车次中的节点。 举个例子,假设车次是a→b→c,那么正确的边应该是a→b和b→c。而用户的代码则会为每个不在车次中的节点x,建立x→a、x→b、x→c的边,这显然错误。这会导致这些车次内的节点之间没有顺序,反而其他节点都必须在它们前面,这显然不符合意。 所以,用户的主要错误在于边的建立方式。正确的做法应该是在同一个车次中,按顺序建立从前一个节点到后一个节点的边。例如,对于车次的序列tt[1], tt[2], ..., tt[t],应该为每个i从1到t-1,添加边tt[i]→tt[i+1]。这样,在拓扑排序时,就能确保后面的节点的层级比前面的大,从而计算出正确的层级数。 而用户现在的代码中,对于每个tt[i],添加了所有不在车次的节点j到tt[i]的边,这显然不符合目中的条件。所以,这应该是导致答案错误的主要原因。 除此之外,可能还有其他问,比如是否在多次处理车次时重复添加边导致in数组被多次累加,或者是否在拓扑排序的时候层级计算有误。但根据当前的分析,主要的错误应该是在边的建立部分。 比如,当处理每个车次时,正确的做法是处理车次内部节点的顺序,而用户代码处理的是车次节点和其他节点的关系。这显然是错误的。 总结:用户的代码在处理每个车次的输入时,错误地将所有不在该车次的节点连接到车次内的每个节点,而正确的做法应该是在车次内的节点之间建立顺序边。因此,用户需要修改建立边的部分,将原来的循环改为处理车次内部的前后节点关系。</think>在分析你的代码后,发现主要问出在**边的建立逻辑**上。目要求处理车站的层级关系时,应按照每个车次的站点顺序建立边,而你的代码错误地将所有不在车次中的站点连接到车次内的站点。以下是详细解释和修正步骤: --- ### **错误原因分析** 1. **边的建立方向错误** - 目中每个车次的站点序列 $S_1, S_2, \dots, S_t$ 表示 $S_1$ 的层级必须低于 $S_2$,$S_2$ 的层级低于 $S_3$,依此类推。因此需要为每个相邻站点建立边 $S_i \rightarrow S_{i+1}$。 - 你的代码中,对于每个车次的站点 $S_i$,会遍历所有不在车次的节点 $j$,建立边 $j \rightarrow S_i$。这会导致大量无关的边,且未处理车次内部的顺序。 --- ### **修正步骤** 1. **调整边的建立逻辑** 对每个车次的站点序列,遍历相邻的站点对,建立从前到后的边。例如,输入车次为 $3,5,6$,应建立 $3 \rightarrow 5$ 和 $5 \rightarrow 6$ 的边。 2. **更新拓扑排序的层级计算** 确保每个节点的层级是前驱节点的最大值加1,而不是直接累加。 --- ### **修正后的代码** ```cpp #include<bits/stdc++.h> using namespace std; const int line=2e5+10, dot=1100; struct edge { int to, next; } e[line]; int ans, cnt=0, n, m, h[dot], in[dot]; bool vis[dot]; void add(int u, int v) { e[++cnt] = {v, h[u]}; h[u] = cnt; in[v]++; // 入度增加 } void tuopu() { queue<pair<int, int>> q; // 存储节点及其层级 vector<int> level(dot, 1); // 初始层级为1 for (int i=1; i<=n; i++) if (in[i] == 0) q.push({i, 1}); while (!q.empty()) { auto [u, l] = q.front(); q.pop(); ans = max(ans, l); for (int i=h[u]; i; i=e[i].next) { int v = e[i].to; if (--in[v] == 0) { level[v] = max(level[v], l+1); q.push({v, level[v]}); } } } } int main() { cin >> n >> m; for (int i=1; i<=m; i++) { int t; cin >> t; vector<int> tt(t); for (int j=0; j<t; j++) cin >> tt[j]; // 建立车次内部的边:tt[j] -> tt[j+1] for (int j=0; j<t-1; j++) { add(tt[j], tt[j+1]); } } tuopu(); cout << ans; return 0; } ``` --- ### **关键改动说明** 1. **边的建立方式** 将原来的双重循环(遍历所有不在车次的节点)改为遍历车次内部相邻的站点,建立从前到后的边。 2. **层级计算优化** 使用 `level` 数组记录每个节点的最大层级,确保正确传递前驱节点的层级。 --- ### **测试用例验证** 假设输入: ``` 6 2 3 1 3 5 3 3 5 4 ``` - 第一个车次:1→3→5 - 第二个车次:3→5→4 建立的边应为:1→3, 3→5, 5→4, 3→5(重复边不影响拓扑排序)。最终层级为1(1)→2(3)→3(5)→4(4),输出4
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值