[DP专栏]来源型DP

今天,我们来聊一聊来源型DP。
比如说,这题
虽然说是IOI1994这样好像很牛的题目,不过好像不难。

一、数字三角形

1.贪心

如果我们走贪心的话,我可以直接给你一个反例:

       1
      0 1
   100 0 1
101   0 0 1

如果走贪心,我们会首先选择大的那边,然后走了4个1。
可是,最优走法是1->0->100->101,答案是202。
为什么贪心会错?因为他只会选择局部的最优,不会去想那一边会不会有更大的数。
贪心的问题在于:目光太短浅。

2.搜索

贪心不行的话……emm我们就爆搜。
可是!n到1000诶!你可以获得若干个TLE,而且代码还比正解长。
最主要是,你不好剪枝。

3.DP

终于请来了我们的主角——DP。
首先我们设计状态:\(f_{i,j}\)表示从第\(i\)行第\(j\)列为开头,得到的最大值。
边界:显然,\(f_{N,i}=a_{N,i}\)。最后一行的每个数得到的最大值都是自己。
状态转移方程:\(f_{i,j}=\max(f_{i+1,j},f_{i+1,j+1})+a_{i,j}\)每个数都可以从下面两个数来。
因为DP要求无后效性,所以我们从下往上计算。
最后答案就是\(f_{1,1}\)。

4.代码

#include<iostream>
using namespace std;
int a[1005][1005];
int f[1005][1005];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) cin>>a[i][j];
	for(int i=n;i>=1;i--){
		for(int j=1;j<=i;j++){
			f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j];
		}
	}
	cout<<f[1][1]<<endl;
	return 0;
}

二、过河卒

题目
如果不管马的阻挡,我们单纯看这个走格子,像什么?
是不是小学的时候数格子的数学题?
是不是有技巧?
是不是每一个位置是他左边加上面?
这就是DP!
最开始\(f_{0,0}=1\),之后遍历每一个点,如果他不是第一行就加上\(f_{i-1,j}\),不是第一列就加上\(f_{i,j-1}\)。

部分代码

dp[0][0]=1;
for(int i=0;i<=n;i++){
	for(int j=0;j<=m;j++){
		if(d[i][j]==false){//判断是否可以走
			if(i){
				dp[i][j]+=dp[i-1][j];
			}
			if(j){
				dp[i][j]+=dp[i][j-1];
			}
		}
	}
}
cout<<dp[n][m]<<endl;

三、总结

来源型DP,就是要有明确的来源,就能做出来。
我们这期就到这了,下期再见~

上一篇:1005 Spell It Right (20分)


下一篇:最大子矩阵 递推 扫描线