AcWing 456 车站分级

AcWing 456 车站分级

一、题目描述

一条 单向 的铁路线上,依次有编号为 1,2,,n 的 n 个火车站。

每个火车站都有一个级别,最低为 1 级。

现有若干趟车次在这条线路上行驶,每一趟都满足如下要求:如果这趟车次停靠了火车站 x,则始发站、终点站之间所有级别 大于等于 火车站 x 的都必须停靠。(注意:起始站和终点站自然也算作事先已知需要停靠的站点

例如,下表是 5 趟车次的运行情况。

其中,前 4 趟车次均满足要求,而第 5 趟车次由于停靠了 3 号火车站(2 级)却未停靠途经的 6 号火车站(亦为 2 级)而不满足要求。

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

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

i+1 行(1im)中,首先是一个正整数 si2sin,表示第 i 趟车次有 si 个停靠站;接下来有 si 个正整数,表示所有停靠站的编号,从小到大排列。

每两个数之间用一个空格隔开。输入保证所有的车次都满足要求。

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

数据范围
1n,m1000

二、题目解析

由于本题中的所有点的权值都是大于0,并且一定满足要求 所有车站都等级森严 不存在环 可以 拓扑排序 得到拓扑图使用递推求解。

本题边权为1,可以使用 拓扑排序 的办法来求极值; 由于求的是最小值,所以需要找最长路径。

建图优化

  • 在建边的时候,最坏情况下是有1000趟火车,每趟有1000个点,每趟上限有500个点停站,则有(1000500)个点不停站,不停站的点都向停站的点连有向边,则总共有5005001000=2.5108,则会超内存

  • 如果用邻接矩阵存储,需要遍历所有的边,遍历的次数也是2.5108,因此会超时,所以在每趟火车所有不停站的点向所有停站的点连有向边时,中间添加一个ver辅助结点,

解释:为啥是500呢?因为共1000个点,a+b=1000,预使ab最大,当然是a=b=500时。

 while (m--) {               // 每一个车次
        ...        
        for (int i = s; i <= e; i++)
            for (int j = s; j <= e; j++)
                if (!b[i] && b[j]) add(i, j, 1), din[j]++; 
    }

看看上面,三层循环,每个最长1000,三层循环放一起,1e9,这是妥妥的TLE啊,不光是空间不行,你时间也不行啊~,三层循环,多么忌讳的东西,除了floyd外,看到这样的代码,基本上就TLE

优化技巧
可以在中间建一个虚拟节点,左边向虚拟点连0,虚拟点向右边连1等价于左边向右边连1

这样只会建O(n+m)条边

优化前:

三、暴力建图+拓扑序

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2010, M = 1000010;
int n, m;
int din[N];
int dist[N];
int b[N]; // 哪个是停靠站

// 通过了 7/10个数据

// 邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

// Topsort求出拓扑序列
vector<int> path;
void topsort() {
    queue<int> q;
    for (int i = 1; i <= n + m; i++)
        if (!din[i]) q.push(i);

    while (q.size()) {
        int u = q.front();
        q.pop();
        path.push_back(u);
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            din[v]--;
            if (din[v] == 0) q.push(v);
        }
    }
}

int main() {
    // 建图
    memset(h, -1, sizeof h);
    scanf("%d %d", &n, &m);     // n个火车站,m趟车次
    while (m--) {               // 每一个车次
        memset(b, 0, sizeof b); // 每个车次可以整理出多条边,此b用来标识哪些是停止点,哪些是不是
        int k;
        scanf("%d", &k);
        int s, e, x;                   // 起点,终点,停靠点
        for (int i = 1; i <= k; i++) { // 读入停靠点
            scanf("%d", &x);
            if (i == 1) s = x; // 第一个停靠点
            if (i == k) e = x; // 最后一个停靠点
            b[x] = 1;          // 记录哪些是停靠点,哪些不是停靠点
        }
        for (int i = s; i <= e; i++)
            for (int j = s; j <= e; j++)
                if (!b[i] && b[j]) add(i, j, 1), din[j]++; // 双层循环,枚举所有非停靠站 向 所有停靠站 连边,边权为1,描述为 i+1->j
    }

    // 求拓扑序
    topsort();

    // 最少是1级
    for (int i = 1; i <= n; i++) dist[i] = 1;

    // 枚举拓扑序列
    for (auto u : path)
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            dist[v] = max(dist[v], dist[u] + w[i]); // 利用拓扑序进行路径最长路递推
        }

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

四、n+m建图+拓扑序

#include <bits/stdc++.h>

using namespace std;
const int N = 2010, M = 1000010;
const int INF = 0x3f3f3f3f;
int n, m;
int in[N];
int dist[N];
int b[N];

// 邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
vector<int> path;
void topsort() {
    queue<int> q;
    // 考虑极限情况:当每一个站点全部停靠的情况下,相当于左侧集合为空(不停靠的没有)
    // 也就没有左侧集合向虚拟点连边这件事,虚拟点的入度为0
    for (int i = 1; i <= n + m; i++)
        if (!in[i]) q.push(i);

    while (q.size()) {
        int u = q.front();
        q.pop();
        path.push_back(u);
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            in[v]--;
            if (in[v] == 0) q.push(v);
        }
    }
}

int main() {
// 文件输入输出
#ifndef ONLINE_JUDGE
    freopen("456.in", "r", stdin);
#endif

    // 建图
    memset(h, -1, sizeof h);
    scanf("%d %d", &n, &m); // n个火车站,m趟车次

    for (int i = 1; i <= m; i++) {
        memset(b, 0, sizeof b); // 记录哪些是停靠点

        int k;
        scanf("%d", &k);
        int s, e;
        for (int j = 1; j <= k; j++) {
            int x; // 每一个停靠站点
            scanf("%d", &x);
            if (j == 1) s = x; // 第一个停靠点
            if (j == k) e = x; // 最后一个停靠点
            b[x] = 1;          // 记录哪些是停靠点,哪些不是停靠点
        }

        int u = n + i; // 分配虚拟点点号
        for (int j = s; j <= e; j++)
            if (!b[j])                 // 如果j不是停靠点
                add(j, u, 0), in[u]++; // 左侧集合向 虚拟点u 连一条长度为0的边
            else
                add(u, j, 1), in[j]++; // 虚拟点向右侧集合中每个点连一条长度为1的边
    }

    // 求拓扑序
    topsort();
    /*
    Q:for (int i = 1; i <= n; i) dist[i] = 1;
    y总这句话为什么不能这样写呀 for (int i = 1; i <= n + m; i) dist[i] = 1;

    A:考虑一种边界情况,当全部站点都停靠的时候,此时虚拟节点是入度为0的节点,虚拟节点的等级dist应该为0,
    因为连向车站的边权已经是1,否则,如果虚拟节点的等级为1,那么连向的车站等级就会是2。
    */
    for (int i = 1; i <= n; i++) dist[i] = 1; // n个站点的等级最少是1,而虚拟节点的等级最少是0

    // 枚举拓扑序列
    for (auto u : path) { // 枚举拓扑路径的每一个点
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            dist[v] = max(dist[v], dist[u] + w[i]);
        }
    }

    int res = 0;
    for (int i = 1; i <= n; i++) res = max(res, dist[i]); // 找出最大值,就是最小等级
    printf("%d\n", res);
    return 0;
}
posted @   糖豆爸爸  阅读(193)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2019-08-17 基于JFinal中搭建wopi协议支撑办法
2014-08-17 转发给吴斌
Live2D
点击右上角即可分享
微信分享提示