Loading

CW 11.13 T4 宝藏

算法

从题面来看, 是一道典型的 \(\rm{TSP}\) 问题的变式

( 不是哥们我也没学过啊你让我做 )

考虑同 \(\rm{TSP}\) 问题的方法, 使用状态压缩 \(\rm{dp}\) 来解决

\(f_{u, state}\) 表示到达点 \(u\) , 现在经过的点集状态压缩后为 \(state\) 的最短路径

考虑由 \(\rm{Subtask}\) 推出正解 ( 然而这个方法只能对好一点的题使用, 抽象题不行 )

部分分

对于 \(\rm{Subtask} \text{ } 2, 3\) , 显然可以直接用 \(\rm{TSP}\) 问题的方法解决

容易的, 有 (其中 \(state\) 的加减法运算是集合处理)

\[f_{u, state} = \min_{\exists (v \to u)} {(f_{v, state - v} + w_{v, u})} \]

注意这个递推式子在具体实现中可以采用刷表法, 不然不好处理

对于 $\rm{Subtask} \text{ } 2 $

答案显然为走过所有点 ( 仅 \(1\) 号节点拥有 )

显然最优答案为 ( \(fullstate\) 是全集 )

\[\min_{i \in [1, n]}{ f_{i, fullstate} + w_{i, 1} } \]

对于 $\rm{Subtask} \text{ } 3 $

显然最优答案为 ( \(fullstate\) 是包含所有钥匙的点集 )

\[\min_{i \in [1, n]}{ f_{i, fullstate} + w_{i, 1} } \]

正解

依旧采用 $\rm{Subtask} \text{ } 2 $ 中的式子, 只是每遇到一个宝箱时都要看当前已经走过的点集中是否拿全了钥匙, 如果没有要跳过这个状态

同样若 \(1\) 号地点为钥匙, 则按 $\rm{Subtask} \text{ } 2 $ 计算答案

\(1\) 号地点为宝箱, 则按 $\rm{Subtask} \text{ } 3 $ 计算答案

代码

#include <bits/stdc++.h>
const int MAXN = 25;
const int MAXSTATE = (1 << 20) + 20;

int n;
int w[MAXN][MAXN];

int op[MAXN];
std::vector<int> Demand[MAXN];

class DP_Class
{
private:
    int dp[MAXN][MAXSTATE];

    /*检查已经可以被开锁*/
    bool check(int State, int NowBox)
    {
        for(int i = 0; i < Demand[NowBox].size(); i++) {
            int Need = Demand[NowBox][i];
            if(!(State & (1 << (Need - 1)))) return false;
        }
        return true;
    }

    /*检查是否包含所有宝箱点*/
    bool checkAll(int State)
    {
        for(int i = 1; i <= n; i++)
            if(op[i] == 0) {
                if(!(State & (1 << (i - 1)))) return false;
                if(!check(State, i)) return false;
            }
                
        
        return true;
    }

public:
    void solve()
    {
        memset(dp, 0x3f, sizeof(dp));
        dp[1][1] = 0;
        
        /*这里每一个点, 都必须由所有包含自己的点集转移而来, 所以考虑外层枚举点集*/
        for(int State = 1; State < (1 << n); State++)
            for(int u = 1; u <= n; u++)
                for(int v = 1; v <= n; v++) {
                    if(u == v) continue;
                    if(!(State & (1 << (u - 1))) || !(State & (1 << (v - 1)))) continue;
                    if(op[v] == 0 && !check(State, v)) continue;
                    
                    dp[v][State] = std::min(dp[v][State], dp[u][State - (1 << (v - 1))] + w[u][v]);
                }

        if(op[1] == 1) {
            int Ans = 0x3f3f3f3f;
            for(int i = 1; i <= n; i++)
                for(int State = 0; State < (1 << n); State++) {
                    if(!checkAll(State)) continue;
                    if (!(State & (1 << (i - 1)))) continue;
                    Ans = std::min(Ans, dp[i][State] + w[i][1]);
                }

            printf("%d", Ans);
        }else {
            int Ans = 0x3f3f3f3f;
            for(int i = 1; i <= n; i++)
                for(int State = 1; State < (1 << n); State++) {
                    if(!check(State, 1)) continue;
                    if(!checkAll(State)) continue;
                    if(!(State & (1 << (i - 1)))) continue;
                    
                    Ans = std::min(Ans, dp[i][State] + w[i][1]);
                }

            printf("%d", Ans);
        }
    }
} DP;

int main()
{
    // freopen("data.in", "r", stdin);
    // freopen("my.out", "w", stdout);

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            scanf("%d", &w[i][j]);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &op[i]);
        if(op[i] == 0) {
            int a, m;
            scanf("%d", &m);
            for(int j = 1; j <= m; j++) {
                scanf("%d", &a);
                Demand[i].push_back(a);
            }
        }
    }

    DP.solve();

    return 0;
}

总结

没有见过这一类型的题

呜呜呜, 这道题的 \(\rm{Subtask}\) 真是太好了

实现的时候思路不够清晰, 多注意 dp 的限制条件

posted @ 2024-11-13 16:31  Yorg  阅读(12)  评论(0)    收藏  举报