UESTC 23-th ACM-ICPC 初赛 Q. 校车

image

这东西其实蛮像最小斯坦纳树,不过我们通过状压DP的思想来讲一讲这个题。

形式化题意:考虑一共有 \(n\) 个点,\(m\) 条无向边,你需要从图中选出至多 \(K\) 条闭合路径,使得所有 \(w\) 个关键点都被覆盖,使得最长路径最小。

容易发现 \(n \le 500\),因此我们可以通过 Floyd 跑出所有点对 \(u, \, v\) 之间的最短路径长度 \(dist(u, \, v)\),我们发现,有用的点的个数只有 \(w\) 个,因此我们存下所有的 \(p_i\) 之后,只有 \(dist(p_i, \, p_j)\) 才是有价值的,因此我们的状态数不会太多。

接下来我们考虑怎么选出一条闭合路径,设 \(d[i][S]\) 表示从 \(1\) 开始,不重复的走到第 \(i\) 个关键点,经过关键点的集合为 \(S\) 的最短路径长度,容易得到

\[d[k][S \cup p_k] = \min\limits_{p_i \in S}\{d[i][S] + dist(p_i, \, p_k)\} \]

我们枚举点的顺序决定了我们的 \(d\) 的值一定是一条路径,而不是一颗树。

然后我们考虑 \(ring[S]\) 表示经过集合 \(S\) 中所有关键点的最短闭合路径长度,它的转移也是直观的

\[ring[S] = \min\limits_{p_i \in S}\{d[i][S] + dist(1, \, p_i)\} \]

如此,我们把所有的闭合路径处理出来后,我们就可以进行我们答案的转移了。

我们定义 \(dp[i][S]\) 表示我们选择了 \(i\) 条闭合路径,覆盖的关键点集为 \(S\) 的最长路径的最小值,那么有

\[dp[i][S] = \min\limits_{T \subseteq S}\{\max\{dp[i - 1][T], \, ring[S / T]\}\} \]

设所有关键点的集合总和是 \(P\),于是我们的答案可以表示为

\[ans = \min\limits_{1 \le i \le k}\{dp[i][P]\} \]

Floyd 跑闭包复杂度是 \(O(n^3)\),最短路径长度更新是 \(O(w^22^w)\),选一条闭合路径的复杂度是 \(O(w2^w)\),子集枚举动规的复杂度是 \(O(k3^w)\),极限数据下,\(k\)\(w\) 同阶,于是总时间复杂度为 \(O(n^3 + (w + w^2)2^w + w3^w)\),计算可得 \(500^3 + (14 + 14^2) \times 2^{14} + 14 \times 3^{14} = 195402206\),最慢的一步是 Floyd 闭包,可以通过。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 16, B = 1 << 16;
int n, m, W, K;
int p[M];
int e[N][N], dis[M][B], ring[B], dp[M][B];

void solve() {
    cin >> n >> m >> W >> K;
    memset(e, 0x3f, sizeof e);
    memset(dis, 0x3f, sizeof dis);
    memset(ring, 0x3f, sizeof ring);
    memset(dp, 0x3f, sizeof dp);
    for (int i = 1; i <= n; i ++ ) e[i][i] = 0;
    for (int i = 1; i <= m; i ++ ) {
        int a, b, c;
        cin >> a >> b >> c;
        e[a][b] = e[b][a] = c;
    }
    for (int k = 1; k <= n; k ++ ) {
        for (int i = 1; i <= n; i ++ ) {
            for (int j = 1; j <= n; j ++ ) {
                e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
            }
        }
    }
    for (int i = 0; i < W; i ++ ) {
        cin >> p[i];
        dis[i][1 << i] = e[1][p[i]];
    }
    for (int k = 0; k < 1 << W; k ++ ) {
        for (int i = 0; i < W; i ++ ) {
            for (int j = 0; j < W; j ++ ) {
                if (!(k >> i & 1)) continue;
                if (k >> j & 1) continue;
                dis[j][k ^ (1 << j)] = min(dis[j][k ^ (1 << j)], dis[i][k] + e[p[i]][p[j]]);
            }
        }
    }
    for (int i = 0; i < W; i ++ ) {
        for (int j = 1; j < 1 << W; j ++ ) {
            if (j >> i & 1) {
                ring[j] = min(ring[j], dis[i][j] + e[1][p[i]]);
            }
        }
    }
    for (int i = 1; i < 1 << W; i ++ ) dp[1][i] = ring[i];
    for (int i = 2; i <= K; i ++ ) {
        for (int S = 1; S < 1 << W; S ++ ) {
            for (int T = S & (S - 1); T; T = (T - 1) & S) {
                dp[i][S] = min(dp[i][S], max(dp[i - 1][T], ring[S ^ T]));
            }
        }
    }
    int ans = 1e9;
    for (int i = 1; i <= K; i ++ ) ans = min(ans, dp[i][(1 << W) - 1]);
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T = 1;
    // cin >> T;
    while (T -- ) solve();
    return 0;
}
posted @ 2025-03-31 14:33  YipChip  阅读(63)  评论(0)    收藏  举报