Q3.5.1.5. 旅游景点's 题解

前言

关键点是状压 DP 。通过数据范围容易联想到,且当找不到一个合适的贪心策略时,就应该往 DP 上思考。

正文

形式化题面:

现在有一个 \(n\) 个点 \(m\) 条边的双向连通图。给出一个 \(k\) ,表示我需要激活的点为 \(2\) 到点 \(k+1\) 。再给出 \(T\) 个限制,格式为 \(u~v\) ,表示在激活 \(u\) 之前一定要先激活 \(v\)​ 。如果一个点没有前置要求点或是前置要求点都已激活,则经过这个点时这个点就被激活。题目保证有解。

数据范围: \(2 \le n \le 2 \times 10^4,1 \le m \le 2 \times 10^5,0 \le k \le 20,0 \le T \le K^2\) ,对于限制的 \(u~v\) ,满足 \(2 \le u,v \le k+1\)

模型判断

观察数据范围,看到 \(k\) 的取值范围,可以联想到状压 DP 或者 dfs 搜索。继续观察,我们通过限制顺序不难联想到拓扑排序,但这就把问题带来了:拓扑序靠后的不一定在拓扑序靠前的后面激活。所以,我们无法用一个贪心策略完成这道题。OK,buff叠满,最终决定使用状压 DP 完成。

算法设计

\(f_{S,ed}\) 表示为激活了的点集为 \(S\) ,且最后一次激活的点为 \(ed\) 。通过预知的能力,我们不难察觉到转移需要最短路计算,所以我们再预处理一个数组 \(dist_{u,v}\) 表示为点 \(u\) 到点 \(v\) 的最短路径。由于我们只在意 \(k\) 个点与点 \(1\) 到其他点的最短路,所以预处理最短路的时间复杂度为 \(\Theta(KM \log M)\) 。又通过简单的思考,发现对于一个局面的合法性,我们需要判断,而判断需要“我”影响的点集,所以预处理出 \(lim_u\) ,表示为被点 \(u\) 限制的点集。

边界: \(f_{\{a\},a} = dist_{1,a} (2 \le a \le k+1)\)

状态转移方程(注意:下式在满足条件 \(lim_{ed}~\cap~S = \emptyset\) 的情况下有效):

\[f_{S,ed} = \min_{ned \in (S - \{ed\})} \{f_{S - \{ed\},ned} + dist_{ned,ed} \} \]

答案: \(\min_{ed \in \{2,3...,k+1\}} (f_{\{2,3...,k+1\},ed} + dist_{ed,n})\)

时间复杂度:\(\Theta(2^MM + KM \log M)\)

代码

#include <cstdio>
#include <algorithm>
#include <string>
#include <queue>
#include <cstring>
#pragma GCC optimize(3)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int ui;
const int MAXSZ = 1 << 20;
char ch, buf[MAXSZ], cuf[MAXSZ], *p1, *p2;
int C;
#define ge() (p1 == p2 && (p2 = buf + fread(p1 = buf, 1, MAXSZ, stdin), p1 == p2) ? EOF : *p1++)
#define pu(c) (C ^ MAXSZ ? cuf[C ++] = c : (fwrite(cuf, 1, MAXSZ, stdout), cuf[(C = 0) ++] = c))
#define Flush() (fwrite(cuf, 1, C, stdout), C = 0)
#define line() pu('\n')
#define space() pu(' ')
#define mset(a, b) memset(a, b, sizeof a)
#define I inline
#define int long long
template <typename T_T>
I void read(T_T &x) {
    x = 0;
    bool flag = false;

    while (ch < '0' || '9' < ch) {
        if (ch == EOF) {
            Flush();
            exit(0);
        }
        ch = ge(), flag |= (ch == '-');
    }
    while ('0' <= ch && ch <= '9') {
        x = x * 10 + (ch ^ 48);
        ch = ge();
    }

    if (flag) x = -x;
}
bool Ccheck(char cha) {
    // 此处填判断条件
}
bool Scheck(char cha) {
    // 此处填判断条件
}
I void cread(char &x) {
    while (!Ccheck(ch)) {
        if (ch == EOF) {
            Flush();
            exit(0);
        }
        ch = ge();
    }
    x = ch, ch = ge();
}
I void sread(std::string &str) {
    str = "";
    while (!Scheck(ch)) {
        if (ch == EOF) {
            Flush();
            exit(0);
        }
        ch = ge();
    }
    while (Scheck(ch)) {
        str += ch;
        ch = ge();
    }
}
template <typename T_T>
I void write(T_T x) {
    if (x < 0) {
        pu('-');
        x = -x;
    }

    if (x > 9) write(x / 10);
    pu(x % 10 | 48);
}
template <typename T_T>
I void Write(T_T x, bool flag = true) {
    write(x);
    if (flag) line();
    else space(); 
}
template <typename T_T>
I T_T max(T_T a, T_T b) { return a > b ? a : b; }
template <typename T_T>
I T_T min(T_T a, T_T b) { return a < b ? a : b; }
template <typename T_T>
I void fmax(T_T &a, T_T b) { a = a > b ? a : b; }
template <typename T_T>
I void fmin(T_T &a, T_T b) { a = a < b ? a : b; }

const int M = 20, M2 = 1 << 20, N = 2e4 + 5, E = 2e5 + 5, INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, lim[M + 2], head[N], dist[M + 6][N], edgem, T, rec[M2][M + 2];
struct Edge {
    int to, w, next;
}edge[E << 1];
void add(int u, int v, int w) {
    edge[++ edgem] = {v, w, head[u]};
    head[u] = edgem;
}

struct pq {
    int u, dist;
    bool operator<(const pq &other) const {
        return dist > other.dist;
    }
};
std::priority_queue <pq> q;
void Dijkstra(int *dist) {
    while (!q.empty()) {
        pq t = q.top(); q.pop();
        int u = t.u, d = t.dist;

        if (d != dist[u]) continue ;

        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].to, w = edge[i].w;
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                q.emplace((pq){v, dist[v]});
            }
        }
    }
}
int f(int S, int ed) {
    if (rec[S][ed] != INF) return rec[S][ed];
    int T = S ^ (1 << ed >> 2);
    if (!T) return dist[1][ed];

    if (T & lim[ed]) return INF;
    for (int nw = 2; nw <= k + 1; ++nw) {
        if (!(T & (1 << nw >> 2))) continue ;
        fmin(rec[S][ed], f(T, nw) + dist[nw][ed]);
    }

    return rec[S][ed];
}

signed main() {
    // freopen("Ryan.in", "r", stdin);
    // freopen("Ryan.out", "w", stdout);

    read(n), read(m), read(k);
    while (m --) {
        int u, v, w; read(u), read(v), read(w);

        add(u, v, w);
        add(v, u, w);
    }

    read(T);
    while (T --) {
        int u, v; read(u), read(v);
        lim[u] |= (1 << v >> 2);
    }

    for (int i = 2; i <= k + 1; ++i) {
        mset(dist[i], 0x3f);
        dist[i][i] = 0;
        q.emplace((pq){i, 0});
        Dijkstra(dist[i]);
    }
    mset(dist[1], 0x3f);
    dist[1][1] = 0;
    q.emplace((pq){1, 0});
    Dijkstra(dist[1]);

    if (!k) {
        Write(dist[1][n]);
        Flush();
        return 0;
    }

    // for (int i = 1; i <= k; ++i)
    //     for (int j = 1; j <= n; ++j)
    //         Write(dist[i][j], j == n);

    mset(rec, 0x3f);
    int ans = INF, S = (1 << k) - 1;
    for (int i = 2; i <= k + 1; i++)
        fmin(ans, f(S, i) + dist[i][n]);

    Write(ans);

    Flush();
    fclose(stdin); fclose(stdout);
    return 0;
}
posted @ 2026-04-05 21:24  Ryan_L_F  阅读(5)  评论(0)    收藏  举报