燃 烧 的 大 脑 — — 动 态 规 划 燃烧的大脑——动态规划 燃烧的大脑——动态规划
题目大意:
解题思路:
挖地雷,拥有最优子结构和无后效性特征,考虑DP。
那么接下来问题又来了,DP怎么推,状态是什么? 阶段是什么?
这时候,我们可以在脑海里建一个图:
把每个地窖看成一个节点,把地窖中的地雷数量看成点权,每个连接的地窖之间可以有一条无权边——这时候就很明显了
地窖之间的关系就是一颗带权的树!
然后我们再把样例套进这棵树,观察最优解在树中的关系,我们不难发现——最优解就是点权和最大的一条树须!由于题目说的可能比较抽象(至少我看到那输入输出完全不理解),但是,只要用笔按照树的规律一画,各个数据之间的关系图就一目了然了!
此时,如果把题目的输入省略,那么这个问题就变成了:
给定一颗带点权的树,求点权和最大的一条树须。
把这个例题转换成这样,这个问题就完成了一半,是不是只要和什么最大值最小值有关就下意识的知道接下来的DP该怎么写了——就下来就是烧脑的DP过程,建议洗把脸观看。
首先尝试设定状态:我们把 d p i dp_{i} dpi 记为如果只挖到地窖编号为 i i i 的时候的最大可挖地雷数。
这时候就要推状态转移方程了,抓稳了。
首先我们考虑某个地窖 i 已经和另外的一堆地窖连接了起来(就是说 i 节点已经是某条树须的某个部分),现在又进来了一个要考虑的地窖组
j
j
j(注意,这里说的地窖可能是一条树须,也就是说正在考虑的地窖可能也已经和另外的地窖连接起来了),那么此时,我们就要考虑地窖
i
i
i 到底要不要和地窖
j
j
j连接,条件是什么呢?条件就是 ——判断如果地窖
i
i
i 和地窖
j
j
j 的连接会不会使得
d
p
i
dp_{i}
dpi 的答案变差,也就是说不能降低
d
p
i
dp_{i}
dpi 的最优解性。
再通俗点讲,就是如果
i
i
i 与
j
j
j 连接后的点权比原本的
d
p
i
dp_{i}
dpi要大,那么就可以通过连接
i
i
i和
j
j
j来更新
d
p
i
dp_i
dpi。
这不,状态转移方程不就出来了吗?
d p i dp_i dpi = m a x max max( d p i dp_i dpi , d p j + a i dp_j+a_i dpj+ai)
最后再来表初始状态,不是有手就行,所有 d p i dp_i dpi 一开始都等于 i i i 的地雷数 a i a_i ai。
状态转移方程都推出来了,输出挖雷路径不是信手拈来的事?只需要在每次更新节点 i i i 的时候都将 i i i 的新父节点记录下来,输出的时候一路向上爬递归输出就行了。
CODE
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
int n,a[30],map[30][30];
int dp[30],sum=0,fre[30],s,ans[30],t=0;
void input()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp[i]=a[i]; //初始化
fre[i]=i;
if(sum<dp[i]) s=i;
sum=max(sum,dp[i]);
}
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
cin>>map[i][j];
}
}
void DP()
{
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(map[i][j]==0) continue;
if(dp[j]<dp[i]+a[j])
fre[j]=i; //记下父节点
dp[j]=max(dp[j],dp[i]+a[j]); //状态转移方程
if(sum<dp[j])
s=j;
sum=max(sum,dp[j]); //点权和
}
}
int i=s;
while(fre[i]!=i)
{
t++;
ans[t]=i;
i=fre[i];
} //循环代替地柜输出
t++;
ans[t]=i;
for(int i=t;i>=1;i--)
cout<<ans[i]<<' ';
cout<<endl<<sum;
}
int main()
{
input();
DP();
return 0;
}
总结:
遇见动归,不要着急写代码,要理清思路,摸到状态转移方程再下手,否则会蒙的!
DP是个博大精深的算法,他非常考验OIer的思维能力,推出状态转移方程更是绝度烧脑,但是当自己做出一道DP题的时候,那种喜悦也是双倍的。
让你们看看我的双倍快乐