[NOIP1996 提高组] 挖地雷 (动态规划)

燃 烧 的 大 脑 — — 动 态 规 划 燃烧的大脑——动态规划

题目大意:

洛谷例题题目传送门!!!


解题思路:

挖地雷,拥有最优子结构和无后效性特征,考虑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题的时候,那种喜悦也是双倍的。

让你们看看我的双倍快乐

猜你喜欢

转载自blog.csdn.net/SAI_2021/article/details/119788912