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 把路径还原出来。

浙公网安备 33010602011771号