Algorithm Notes

LJXin

记录算法刷题、竞赛思路与编程成长,希望每一篇都讲清楚一点、写扎实一点。

P2196 找地雷

P2196 找地雷

原题链接

题目范围

  • \(1 \le n \le 20\)

解题思路

n<=20,bfs 或 dp 都能做,这里我写 dp。

dp[i] 表示以第 i 个点为起点时,能够获得的最大地雷数;
nxt[i] 表示在最优情况下,i 的下一个点是谁,用来最后还原路径。

转移很好理解:

\[dp[i] = \max\{dp[j] + a[i] \mid f[i][j] = 1,\ j>i\} \]

如果从 i 可以走到 j,那就可以先拿第 i 个点的地雷,再接上从 j 开始的最优结果。

因为转移只会用到后面的点,也就是 j > i,所以 dp 要从后往前算。
全部算完后,再找最大的 dp[i],这个 i 就是最优路径的起点,然后顺着 nxt 一直输出即可。

复杂度分析

  • 时间复杂度:\(O(n^2)\)
  • 空间复杂度:\(O(n^2)\)

本题 n 不大,DFS 和 DP 都能过。这里主要开销就是状态转移和存图。

代码实现

#define rep(i,a,b) for(int i=(int)a;i<int(b);++i)
bool f[21][21];      // 路径
int a[21];           // 地雷数
int dp[21];   // dp[i]表示以i为起点的最大地雷数
int nxt[21];  // nxt[i]表示dp[i]的下一个点
int start;
void solve(){
    int n;cin>>n;
    //初始化
    rep(i,1,n+1)cin>>a[i];
    rep(i,1,n)rep(j,i+1,n+1)cin>>f[i][j];
    // 从后往前dp
    for(int i=n;i>=1;--i){
        dp[i]=a[i];
        for(int j=i+1;j<=n;++j){
            if(f[i][j]){
                if(dp[i]<dp[j]+a[i]) {
                    dp[i] = dp[j] + a[i];
                    nxt[i] = j;
                }
            }
        }
    }
    start =1;
    // 找到最大的dp[i](起点)
    rep(i,2,n+1)if(dp[start]<dp[i])start=i;
    
    int res=start;

    while(start){
        cout<<start<<" ";//输出路径
        start=nxt[start];
    }
    cout<<"\n"<<dp[res]<<"\n";
}

易错点

  • 因为 dp[i] 会用到后面的状态,所以要从后往前处理。
  • nxt[i] 记录的是最优转移到的下一个点,别只算最大值忘了存路径。
  • 最后输出路径时,要从最大 dp[i] 对应的起点开始顺着 nxt 走。
  • dp[i] 初值要先设成 a[i],表示后面一个点都不走,只选当前点。

总结

关键是看出图本身是有方向的,而且边只会从前面的点连到后面的点。
这样一来它其实就是一个 DAG 上的路径 DP,按从后往前的顺序转移,再用 nxt 把路径还原出来。

posted @ 2026-03-29 11:43  kliu_365  阅读(35)  评论(0)    收藏  举报
TOP