动态规划的引入

动态规划的引入

引言

可以将动态规划的核心理解为 " 记录 " 和 " 自底向上 "

对于一个问题,如果这个问题能够拆解为同类型的有限个子问题,并且最终的结果能够由这些子问题得到,则可以使用动态规划。
动态规划的实现思想为 " 自底向上 " ,即先求出最小子问题的结果,然后通过这个结果又得到父问题的结果,以此类推,直到得到最终结果。

当然动态规划也可以是 " 记录 " ,即把需要重复计算的值存储下来,等到需要使用的时候就直接用
这样可以减少时间【尤其是在DFS有关的题目中会大大减少耗时】

路径问题

经典路径问题

​ 经典的路径问题给人的感觉更偏重于 " 记录 "
​ 主要是记录每个节点的最大结果,然后用这个结果去求下一个节点的最大结果

洛谷 - P1216 数字三角形 Number Triangles

洛谷 - P1216 数字三角形 Number Triangles

题目描述

​ 观察下面的数字金字塔。

​ 写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。
​ 每一步可以走到左下方的点也可以到达右下方的点。

        7 
      3   8 
    8   1   0 
  2   7   4   4 
4   5   2   6   5 

输入格式

​ 第一个行一个正整数 r ,表示行的数目。
​ 后面每行为这个数字金字塔特定行包含的整数。

输出格式

​ 单独的一行,包含那个可能得到的最大的和。

样例输入 #1

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

样例输出 #1

30

AC CODE

int main()
{
	int n, amap[100][100], flag[100][100];
	cin>>n;
	for(int h=1; h<=n; h++)
		for(int i=1; i<=h; i++)
			cin>>amap[h][i];
	flag[1][1] = amap[1][1];
	for(int h=2; h<=n; h++)
	{
		for(int i=1; i<=h; i++)
		{
			if(i == 1)
				flag[h][i] = flag[h-1][1] + amap[h][i];
			else if(i == h)	
				flag[h][i] = flag[h-1][i-1] + amap[h][i];
			else
				flag[h][i] = amap[h][i] + max(flag[h-1][i], flag[h-1][i-1]);
		}
	}
	int anser = 0;
	for(int h=1; h<=n; h++)
		if(anser < flag[n][h])	anser = flag[n][h];
	cout<<anser<<endl;
	return 0;
} 

需要记录具体方案的问题

记录具体方案的情况需要明白一点:并不需要将方案全部记录下来

即,对于某个节点的最佳方案,并不需要像数组一样记录它的值,是可以记录 它是怎么获得的 或者是 它下一步是去哪里 ,最后再输出方案的时候进行条件判断即可【可以使用递归输出】

洛谷 - P2196 挖地雷

洛谷 - P2196 挖地雷

题目描述

​ 在一个地图上有N个地窖,每个地窖中埋有一定数量的地雷。
​ 同时,给出地窖之间的连接路径。
​ 当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,
​ 然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。
​ 设计一个挖地雷的方案,使某人能挖到最多的地雷。

样例输入 #1

5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1

样例输出 #1

1 3 4 5
27

AC CODE

int n;	// 地窖的数量
int boom_num[Maxsize];	// 每个地窖中地雷的个数
int amap[Maxsize][Maxsize];	// 地窖连接情况
int record[Maxsize];	// 到第 i 个地窖时能获得的最大地雷数
int read_[Maxsize];	// 第 i 个地窖时上一步的地窖 
void FindOrig(int h)
{
	if(read_[h] == -1) 
	{
		cout<<h<<" ";
		return ; 
	}
	FindOrig(read_[h]);
	cout<<h<<" ";
}

int main()
{
	cin>>n;
	for(int h=1; h<=n; h++)	cin>>boom_num[h];
	for(int h=1; h<=n; h++)
		for(int i=h+1; i<=n; i++)
			cin>>amap[h][i];
	record[0] = 0;
	record[1] = boom_num[1];
	for(int h=1; h<=n; h++)	read_[h] = -1;	// 没有地窖能够通往第 h 个地窖 
	for(int h=2; h<=n; h++)
	{
		int tempmax = 0, pos = -1;
		for(int i=1; i<h; i++) 
		{
			if(amap[i][h]) 
			{	// 第 i 个地窖能够达到第 h 个地窖
				if(record[i] > tempmax) 
				{
					tempmax = record[i];
					pos = i;
				}
			}
		}
		record[h] = tempmax + boom_num[h];
		read_[h] = pos; 
	}
	int anspos = 0, temp = 0;
	for(int h=1; h<=n; h++)
	{
		if(record[h] > temp)
		{
			temp = record[h];
			anspos = h;
		}
	}
	FindOrig(anspos);
	cout<<endl<<record[anspos];
	return 0;
} 

与DFS有关的问题

与DFS相关的动态规划可以完全理解为DFS,只是在求每一个节点值的时候将它记录下来

洛谷 - P1434 滑雪

洛谷 - P1434 滑雪

题目描述

​ Michael 喜欢滑雪。这并不奇怪,因为滑雪的确很刺激。
​ Michael 想知道在一个区域中最长的滑坡。
​ 区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子:

1   2   3   4   5
16  17  18  19  6
15  24  25  20  7
14  23  22  21  8
13  12  11  10  9

​ 一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度会减小。
​ 在上面的例子中,一条可行的滑坡为 24-17-16-1(从 24 开始,在 1 结束)。

输入格式

​ 输入的第一行为表示区域的二维数组的行数 R 和列数 C。
​ 下面是 R 行,每行有 C 个数,代表高度(两个数字之间用 1 个空格间隔)。

输出格式

​ 输出区域中最长滑坡的长度。

样例输入 #1

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

样例输出 #1

25

AC CODE

int amap[107][107];	// 输入数据 
int record[107][107];	// h i 点开始,能滑的最大长度 
int m, n, anser = 0;

int DFS(int x, int y)
{
	if(x <= 0 || y <= 0 || x > m || y > n)	// 超出边界
		return 0;
	if(!record[x][y]) 
	{
		int max_around = 0;
		if(amap[x][y] < amap[x-1][y])
			max_around = max(max_around, DFS(x-1, y));
		if(amap[x][y] < amap[x+1][y])
			max_around = max(max_around, DFS(x+1, y));
		if(amap[x][y] < amap[x][y-1])
			max_around = max(max_around, DFS(x, y-1));
		if(amap[x][y] < amap[x][y+1])
			max_around = max(max_around, DFS(x, y+1));
		record[x][y] = 1 + max_around;
	}
	return record[x][y];
} 

int main()
{
	cin>>m>>n;
	for(int h=1; h<=m; h++) 
	{
		for(int i=1; i<=n; i++) 
		{
			cin>>amap[h][i];
			record[h][i] = 0;
		}
	}
	for(int h=1; h<=m; h++)
	{
		for(int i=1; i<=n; i++)
		{
			record[h][i] = DFS(h, i);
			if(anser < record[h][i])
				anser = record[h][i];
		}
	}
	cout<<anser<<endl;
	return 0;
}

0-1背包

0-1背包的核心:a[h][i] = max(a[h][i-1], a[h-item.volume][i-1] + item.value);

即,先记录使用 h-item. volume空间,拿前 i-1 种物品的最大价值结果
则,在使用 h 空间拿前 i 种物品时,要么放入第 i 个物品,要么不放
那么,如果要放入这个物品,则得到的价值为 a[h-item.volume][i-1] + item.value
如果不放,则其价值为 a[h][i-1]

经典0-1背包

洛谷 - P1048 采药

洛谷 - P1048 采药

题目描述

​ 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。
​ 为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。
​ 医师把他带到一个到处都是草药的山洞里对他说:
​ “孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。
​ 我会给你一段时间,在这段时间里,你可以采到一些草药。
​ 如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

​ 如果你是辰辰,你能完成这个任务吗?

样例输入 #1

70 3
71 100
69 1
1 2

样例输出 #1

3

AC CODE

struct item
{
    int gettime;
    int val;
};
int max(int a, int b)
{
    if(a > b) return a;
    return b;
}

int main()
{
    int time, num;
    int record[1007][107]; // 记录最大价值
    item itemlist[107];
    cin>>time>>num;
    for(int h=1; h<=num; h++)
            cin>>itemlist[h].gettime>>itemlist[h].val;
    for(int h=0; h<=num; h++)   record[0][h] = 0;
    for(int h=0; h<=time; h++ ) record[h][0] = 0;
    for(int h=1; h<=time; h++)
    {
        for(int i=1; i<=num; i++)
        {
            if(h >= itemlist[i].gettime)
            {
                int temp = record[h-itemlist[i].gettime][i-1] + itemlist[i].val;
                record[h][i] = max(record[h][i-1], temp);
            }
            else
            {
                record[h][i] = record[h][i-1];
            }
        }
    }
    cout<<record[time][num]<<endl;
    return 0;
}

最大子段和类型

最大子段和

洛谷 - P1115 最大子段和

洛谷 - P1115 最大子段和

题目描述

​ 给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

输入格式

​ 第一行是一个整数,表示序列的长度 n。
​ 第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字。

输出格式

​ 输出一行一个整数表示答案。

样例输入 #1

7
2 -4 3 -1 2 -4 3

样例输出 #1

4

AC CODE

int main()
{
	// record 记录前 i 个数据的最大和 
	int n, array[MaxSize], record[MaxSize], anser = -99999999;	
	cin>>n;
	for(int h=0; h<n; h++) cin>>array[h];
	record[0] = array[0];
	for(int h=1; h<n; h++)
	{
		if(record[h-1] + array[h] > array[h])
			record[h] = record[h-1] + array[h];	
		else
			record[h] = array[h];
		if(anser < record[h])	anser = record[h];
	} 
	if(anser < record[0])	anser = record[0];
	cout<<anser<<endl;
	return 0;
} 

划分数组类型

洛谷 - 1025 数的划分

洛谷 - 1025 数的划分

题目描述

​ 将整数n分成k份,且每份不能为空,任意两个方案不相同(不考虑顺序)。
​ 例如:n=7,k=3,下面三种分法被认为是相同的。

​ 1,1,5;
​ 1,5,1;
​ 5,1,1;

​ 问有多少种不同的分法。

输入格式

​ n,k (6< n < 200,2 < k < 6)

输出格式

​ 1 个整数,即不同的分法。

样例输入 #1

7 3

样例输出 #1

4

AC CODE

import java.util.Scanner;

class dpMap {
    public int num;
    public int piece;
    public int[][] piece_map;

    public dpMap(int n, int p) {
        this.num = n;
        this.piece = p;
        this.piece_map = new int[n + 1][p + 1];
        for (int h = 0; h <= n; h++) {
            this.piece_map[h][1] = 1; // 把h个1分成1份的方案数为1
        }
        for (int h = 0; h <= p; h++) {
            this.piece_map[h][h] = 1; // 把h个1分成h份的方案数为1
        }
        for (int h = 0; h <= n; h++) {
            this.piece_map[h][0] = 0;
        }
        for (int h = 0; h <= p; h++) {
            this.piece_map[0][h] = 0;
        }
    }
    
    public void dpPieceMap() {
        for (int h = 2; h <= this.num; h++) {
            for (int i = 2; i <= this.piece; i++) {
                if (h == i) {
                    this.piece_map[h][i] = 1;
                } else if (h < i) {
                    this.piece_map[h][i] = 0;
                } else {
                    for (int j = 1; j <= i; j++) {
                        this.piece_map[h][i] += this.piece_map[h - i][j];
                    }
                }
            }
        }
    }
}

public class LuoGu_P1025 {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner(System.in)) {
            int num = scanner.nextInt(), piece = scanner.nextInt();
            dpMap dpmap = new dpMap(num, piece);
            dpmap.dpPieceMap();
            for (int h = 0; h <= num; h++) {
                for (int i = 0; i <= piece; i++) {
                    System.out.print(dpmap.piece_map[h][i] + " ");
                }
                System.out.println();
            }
            System.out.println(dpmap.piece_map[num][piece]);
        }
    }
}

动态策略:
当数多于盘子时:piece_map[h][i] = piece_map_cannull[h-i][i];
piece_map_cannull[h-i][i] = Σpiece_map[h][j];(j∈[1,i])

posted @ 2024-04-19 10:01  木槐muhuai  阅读(22)  评论(0编辑  收藏  举报