【ACWing】456. 车站分级

题目地址:

https://www.acwing.com/problem/content/458/

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

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

输出格式:
输出只有一行,包含一个正整数,即 n n n个火车站最少划分的级别数。

数据范围:
1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000

对每个列车的停靠站点做分析,那么首尾两个站点即为始发站和终点站,中间停留的站点的级别一定都严格大于未停留的站点的级别。很显然是个差分约束问题,那么要求最小的级别数,就是要求差分约束的最长路。由于输入保证所有车次都满足要求,所以图是没有环的,此时可以不用SPFA来做,而用拓扑排序来做。但是这里,例如有个车总共经过了 k k k个站点,停靠了 i i i次,如果正常建图,需要连 i ( k − i ) i(k-i) i(ki)条边,这样太耗费时间和空间了,可以采取另一种方法,在两组点之间设一个虚拟点,从左边每个点连到这个虚拟点一条边,权为 0 0 0,然后再从这个虚拟点连到右边每个点一条边,权为 1 1 1,这样边数就是 k k k,产生的图依然是拓扑图,依然可以求最长路。求最长路的话,要按拓扑序更新每个点的最长路径长度。代码如下:

#include <iostream>
#include <cstring>
using namespace std;

// 要开1000平方条边,点数不光有车站数,还要加上线路数,线路数就是虚拟点数
const int N = 2010, M = 1000010;
int n, m;
int h[N], e[M], ne[M], w[M], idx;
int q[N];
// 拓扑排序统计入度
int d[N];
int dist[N];
bool st[N];

void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
    // b的入度加1
    d[b]++;
}

void topsort() {
    int hh = 0, tt = 0;
    for (int i = 1; i <= n + m; i++)
        if (!d[i])
            q[tt++] = i;
    
    while (hh < tt) {
        int t = q[hh++];
        for (int i = h[t]; ~i; i = ne[i]) {
            int v = e[i];
            if (--d[v] == 0) q[tt++] = v;
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= m; i++) {
        memset(st, 0, sizeof st);
        int cnt;
        scanf("%d", &cnt);
        int start = n, end = 1;
        while (cnt--) {
            int stop;
            scanf("%d", &stop);
            start = min(start, stop);
            end = max(end, stop);
            st[stop] = true;
        }

		// 建虚拟点
        int v = n + i;
        for (int j = start; j <= end; j++) 
            if (!st[j]) add(j, v, 0);
            else add(v, j, 1);
    }

    topsort();

    for (int i = 1; i <= n; i++) dist[i] = 1;
    for (int i = 0; i < n + m; i++) {
        int v = q[i];
        for (int j = h[v]; ~j; j = ne[j])
            dist[e[j]] = max(dist[e[j]], dist[v] + w[j]);
    }

    int res = 0;
    for (int i = 1; i <= n; i++) res = max(res, dist[i]);
    printf("%d\n", res);

    return 0;
}

时间复杂度 O ( N + M ) O(N+M) O(N+M),空间 O ( N ) O(N) O(N) N N N M M M分别是图的点数和边数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值