杂谈:
递推和递归的一个比较重要的区别是,递推需要先求出所有前提条件,比如
dp[i] = max(dp[i], dp[j] + num[i]);
需要先求出dp[ j ],这个时候就需要规划一下遍历的顺序,让dp[ j ]先被求出。
而递归过程中,若
m = max(m, dp(j) + num[i]);
则只需要在递归函数的适当位置做好return即可。
dp[i]表示以第i号地洞为起点,这样做的好处是方便后面输出路径,不过这样就需要从后往前遍历地洞,如此才能保证递推公式里用到的前提条件提前被求出来过。
以这道题为例,递推公式为:
前提条件指的是dp[ j ],也就是以第 j 号地洞为起点,能得到的最大地雷数。
#include<iostream>
using namespace std;
int N;
int num[25]; // num[i]记录第i号地洞的地雷数
bool can[25][25]; // can[i][j]表示第i号地洞能否到达第j号地洞
int dp[25]; // dp[i]表示以第i号地洞为起点的地雷最大值
int nextDot[25]; // nextDot[i]表示第i号地洞的下一个地洞是哪个
// 用于后面输出路径
int main()
{
scanf("%d", &N);
for(int i=1;i<=N;++i)
{
scanf("%d", &num[i]);
dp[i] = num[i]; // 初始化dp[i]为i号地洞里的地雷数
}
for(int i=1;i<=N;++i)
for(int j=i+1;j<=N;++j)
cin>>can[i][j];
int m = 0;
int start = 0; // 标记从哪个地洞开始走,可以得到最多地雷
for(int i=N;i>0;--i) // 从后往前遍历地洞,保证递推公式里的前提条件被求出来过
{
for(int j=i+1;j<=N;++j)
// 因为是从一个地洞不断往下走,因此下一个地洞的编号需要从大于i开始
{
if(can[i][j] && dp[j] + num[i] > dp[i])
{
dp[i] = dp[j] + num[i];
nextDot[i] = j; // 表示 i号地洞的下一个洞是 j
}
}
if(dp[i] > m)
{
m = dp[i];
start = i; // 更新起始点
}
}
while(start) // 输出路径
{
cout<<start<<' ';
start = nextDot[start];
}
cout<<endl<<m;
return 0;
}
下面是dp[ i ]表示以 i 号地洞为终点时能得到的最大地雷数的做法,需要格外注意的是这种写法下,输出路径需要反向输出,这里采用了递归输出的方法,这种方法可以掌握一下;另外就是两种写法遍历地洞的顺序有所不同
#include<iostream>
#include<string.h>
using namespace std;
int f[25]; // f[i] 表示以i号地洞为终点时能得到的最大地雷数
int can[25][25];
int num[25];
int pre[25];
int N;
void dfs(int x)
{
if(pre[x]) dfs(pre[x]);
cout<<x<<' ';
}
int main()
{
scanf("%d", &N);
for(int i=1;i<=N;++i) scanf("%d", &num[i]);
for(int i=1;i<N;++i)
for(int j=i+1;j<=N;++j)
scanf("%d", &can[i][j]);
memset(f, -1, sizeof(f));
int m = 0;
int pos = 0;
for(int k=1;k<=N;++k)
{
if(f[k] == -1)
f[k] = num[k];
for(int i=k-1;i>0;--i)
{
if(can[i][k] && f[i] + num[k] > f[k])
{
f[k] = f[i] + num[k];
pre[k] = i; // 记录k号地洞的上一个洞是i
}
}
if(f[k] > m)
{
m = f[k];
pos = k;
}
}
dfs(pos);
cout<<endl<<m;
return 0;
}