• NOIP2013普及组 车站分级


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

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

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

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

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

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

    输入格式

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

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

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

    输出格式

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

    数据范围

    1≤n,m≤1000

    输入样例:

    1. 9 3
    2. 4 1 3 5 6
    3. 3 3 5 6
    4. 3 1 5 9

    输出样例:

    3
    
    难度:中等
    时/空限制:1s / 64MB
    总通过数:2510
    总尝试数:5347
    来源:NOIP2013普及组
    算法标签

    解题思路:

    由题意停靠的车站必然比非停靠的车站级别高

    因此我们可以把一条线路中非停靠站和停靠站连一条权值为1的边:如样例:1,3,5,6.

    所连接的边为:

     

     因为题目保证一定有解所以该图肯定无环即为拓扑图

    但是这样建图的一个车次的边数为n * (1000 - n)(即非停靠点的点数n * 剩下的点数即停靠点的点数(1000 - n))最多有1000个车次即共需要建1000 * n * (1000 - n)条边,当n取500是上述式子取最大值即为1000 * 500 * 500这样肯定会tle所以不能这样建边。

    这里有一个非常常用的算法,在非停靠点和停靠点之间建立一个虚拟点,非停靠点到虚拟点的权值定义为0,虚拟点到停靠点的权值定义为1:

    如图:

    这样的建边方式与上述的建边方式是等价的,因此所建立的边数从n * (1000 - n)变为n + 1000 - n; 因此边数最多为为1000  * (500 + 500)

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. const int N = 2010, M = 1000 * 1000 + 10;
    6. int n, m;
    7. int d[N];//记录一个点的入度
    8. bool st[N];//标记该点是否为停靠点
    9. int dist[N];//dist[i]表示i这个点级别最低是多少
    10. int q[N], hh, tt = -1;//拓扑排序数组
    11. int h[N], e[M], ne[M], w[M],idx;//邻接表数组
    12. void add(int a, int b, int c)//邻接表模板
    13. {
    14. d[b] ++ ;//b入度加1
    15. e[idx] = b;
    16. w[idx] = c;
    17. ne[idx] = h[a];
    18. h[a] = idx;
    19. idx ++ ;
    20. }
    21. void topsort()//拓扑排序模板
    22. {
    23. for (int i = 1; i <= n + m; i ++ )//要算上一个虚拟点
    24. if (!d[i]) q[++ tt] = i;
    25. while (hh <= tt)
    26. {
    27. int t = q[hh ++ ];
    28. for (int i = h[t]; i != -1; i = ne[i])
    29. {
    30. int j = e[i];
    31. if (-- d[j] == 0) q[++ tt] = j;
    32. }
    33. }
    34. }
    35. int main()
    36. {
    37. cin >> n >> m;
    38. memset(h, -1, sizeof h);
    39. for (int i = 1; i <= m; i ++ )
    40. {
    41. int cnt;
    42. scanf("%d", &cnt);
    43. memset(st, 0, sizeof st);
    44. int start = n, last = 1;//表示该车次从哪个车站开始到哪个车站结束
    45. while (cnt -- )
    46. {
    47. int stop;
    48. scanf("%d", &stop);
    49. st[stop] = true;//表示stop为停靠点
    50. start = min(start, stop);
    51. last = max(last, stop);
    52. }
    53. int ver = n + i;//在非停靠边和停靠边之间建立一个虚拟点
    54. for (int j = start; j <= last; j ++ )//从start开始到last结束
    55. if (st[j]) add(ver, j, 1);
    56. else add(j, ver, 0);
    57. }
    58. topsort();
    59. for (int i = 1; i <= n; i ++ ) dist[i] = 1;//每个车站级别最低为1,这里也可以建立一个虚拟源点,虚拟源点到每个点的权值为1
    60. for (int i = 0; i < n + m; i ++ )//加上虚拟源点
    61. {
    62. int j = q[i];
    63. for (int k = h[j]; k != -1; k = ne[k])
    64. dist[e[k]] = max(dist[e[k]], dist[j] + w[k]);//求最小的级别要用最长路,这里和差分约束的思想一样
    65. }
    66. int res = 0;
    67. for (int i = 1; i <= n; i ++ ) res = max(res, dist[i]);//找出所有符合条件的级别中最大的那个
    68. cout << res << endl;
    69. return 0;
    70. }

     若不了解这句话(dist[e[k]] = max(dist[e[k]], dist[j] + w[k]);)的同学可以看看差分约束的思想:

    (1条消息) 《图论:差分约束算法详解 + 算法证明》+ 模板题:糖果_wsh1931的博客-CSDN博客

  • 相关阅读:
    基于若依框架进行二次开发优化指南
    vue3 ref的使用
    One-YOLOv5 v1.2.0发布:支持分类、检测、实例分割
    力扣(322.279)补8.1
    PO接口日志 RSXMB_SHOW_STATUS 统计消息状态概览
    记录一下MySql update会锁定哪些范围的数据
    MySQL(四)——正则表达式查询、插入数据、删除数据
    四大组件---ContentResolver
    发明专利和实用新型专利的区别
    “文本界面”(Python插值字符串格式化打造)
  • 原文地址:https://blog.csdn.net/qq_61935738/article/details/126849339