P1983 [NOIP2013 普及组] 车站分级 | C++

107 阅读4分钟

题意分析

题目要求给定 n 个车站分配"等级"(用整数表示),并满足所有给定车次的要求:

  • 如果某趟车在区间 [si,ei][s_i, e_i] 内停靠了某个等级为 LL 的车站,则该区间内所有等级 L\geq L 的车站也必须停靠。
  • 如果某车站被跳过,则其等级必须小于区间内被停靠车站的最低等级。

等价地说:

  1. 对于任意一趟车,其在 [si,ei][s_i, e_i] 范围内实际停靠的车站(记作集合 TiT_i)都具有较高或相同的等级;
  2. 而在该区间内被跳过的车站(记作 SiTiS_i \setminus T_i)的等级严格小于 TiT_i 中所有车站的等级。

这可以转化为一个偏序关系

  • 若车站 xx 被跳过且车站 yy 被停靠,则我们必须有 Level(x)<Level(y)Level(x) < Level(y)
  • 在偏序图中,可记作一条有向边 xyx \to y,表示"xx 的等级小于 yy 的等级"。

构造出所有"跳过站 \to 停靠站"的有向边后,我们得到一个有向图(如果存在可行解,这个图就是一个有向无环图 / DAG)。要满足这些等级不等式且使用的等级数最少,就等价于在这个 DAG 中寻找满足"有向边单调递增"要求的整数标号方案,且使用的不同整数值最少。


结论

在有向无环图中,"所需的最少不同标号数"恰好等于该图的最长路径长度(也称 height 或最大链长度)。

  1. 如果图中存在一条长度为 kk 的链 (v1v2vk)(v_1 \to v_2 \to \dots \to v_k),它们的等级必须是严格递增的,至少需要 kk 个不同整数。
  2. 用拓扑排序 + 动态规划的方法可以计算最长路径长度。

求解步骤

  1. 读入数据,有 nn 个车站、mm 趟车。
  2. 对每趟车:
    • 读出停靠站集合 TiT_i(按升序给出),令区间 [si,ei][s_i, e_i] 表示从最小停靠站编号到最大停靠站编号;
    • 把在此区间内但不在 TiT_i 中的车站记为跳过站,每个跳过站 xxTiT_i 中的任意车站 yy 产生一条有向边 xyx \to y
  3. 构造出一个最多包含 nn 个节点的有向图(顶点对应车站)。
  4. 在这个图上做最长路径计算(题意保证图无环):
    • 用拓扑排序(Kahn 算法)或 DFS-DP 来求最长路径。
    • 记录最长路径长度作为最少划分的等级数。

C++ 参考实现

#include <bits/stdc++.h>
using namespace std;

static const int MAXN = 1000;

bitset<MAXN> adj[MAXN];  // adj[u][v] = 1 表示 u -> v (等级u < 等级v)

// 拓扑 + 最长路 DP
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    // 读入每趟车信息,构造图
    for(int i=0; i<m; i++){
        int s;
        cin >> s;
        vector<int> stops(s);
        for(int j=0; j<s; j++){
            cin >> stops[j];
            // 转为 0-based
            stops[j]--;
        }
        // 区间 [st..en]
        int st = stops[0], en = stops[s-1];

        // 把停靠站做成一个 bitset
        bitset<MAXN> routeStops;
        for(int stid: stops){
            routeStops[stid] = 1;
        }
        // 对区间内的每个车站 x,如果 x 未被停靠,则对 T_i 所有停靠站 y 产生 x->y
        for(int x = st; x <= en; x++){
            if(!routeStops[x]){
                // x 在本次车次被跳过
                // 对所有 y in T_i, adj[x][y] = 1
                adj[x] |= routeStops;
            }
        }
    }

    // 计算每个点的入度
    vector<int> inDegree(n,0);
    for(int u=0; u<n; u++){
        // 遍历 bitset, 对置1的位置 v, inDegree[v]++
        auto & bs = adj[u];
        for(int v = bs._Find_first(); v < n; v = bs._Find_next(v)){
            inDegree[v]++;
        }
    }

    // 拓扑排序 + dp[u] 表示以 u 结尾的最长链长度
    vector<int> dp(n,1);
    queue<int> q;
    // 入度为0的先入队
    for(int v=0; v<n; v++){
        if(inDegree[v] == 0){
            q.push(v);
        }
    }

    int ans = 1; // 最终最长路长度
    while(!q.empty()){
        int u = q.front();
        q.pop();

        // u -> v
        auto &bs = adj[u];
        for(int v = bs._Find_first(); v < n; v = bs._Find_next(v)){
            // 更新 dp[v]
            dp[v] = max(dp[v], dp[u] + 1);
            ans = max(ans, dp[v]);
            inDegree[v]--;
            if(inDegree[v] == 0) {
                q.push(v);
            }
        }
    }

    cout << ans << "n";
    return 0;
}

说明

  1. 图的存储:

    • 用大小为 nnstd::bitset<1000> 数组 adj[] 来存储图的邻接关系。
    • adj[u][v] = 1 表示存在边 (uv)(u \to v)
  2. 最长路径求法:

    • 使用拓扑排序 + 动态规划。
    • dp[v] 表示以节点 vv 为结尾的最长链长度。
    • 对每条边 uvu \to v,松弛更新 dp[v] = max(dp[v], dp[u] + 1)
  3. 最少等级数:

    • 题目保证图无环,因此可以通过最长路径长度确定最少等级数。
    • 输出最长路径长度即为答案。