[AcWing蓝桥杯]之复杂DP(C++题解)

目录

鸣人的影分身(线性DP)

DFS解法

 DP(完全背包)

思维解法递归解法

包子凑数(完全背包+数论)

糖果(01背包问题)

密码脱落(区间DP+数学)

括号配对(区间DP)

生命之树(树形DP)

旅游规划(树形DP)


前景提要:

(1)dp数组的含义:这个dp数组代表的意义是什么,[i][j]又分别代表什么意思

(2)dp数组的属性:包括最大值,最小值,方案数,次数,即dp数组本身存的数

(3)dp数组的初始化:根据它的含义进行合理的初始化

(4)dp数组的遍历顺序:是正序从前往后,还是后续遍历防止被重复覆盖

(5)dp数组的递推公式:从最后一步反推,即要得到这一步,需要上一步进行的操作

(6)dp数组的返回值:即题目要求的是dp数组的什么,要根据含义来


鸣人的影分身(线性DP)

1050. 鸣人的影分身 - AcWing题库

思路解析:

透过题目看本质,这道题的本质是:

给出一个和,给出一个份数,求出“和”可以被分成多少个份数大小的集合

举个例子:总和为:7   份数为:3

那么就有:(注意顺序不同,集合内的数字相同被视为一种情况)

(7,0,0)

( 6,1,0)  

(5,2,0) (5,1,1)

(4,3,0) (4,2,1)

(3,3,1)  (3,2,2)

DFS解法

相信看完例子解释的你,已经想到了DFS,来暴力搜索

DFS代码如下:

DFS的核心之一就是参数的意义

(1)首先要输入总能量,和分身的数量

那么在向下搜索的过程中:

(2)思考深搜过程:

<1>需要一个标志变量,来标记当前从这个标志变量开始枚举,也就是变量start

这个就类似于(1~5)个数中(集合大小为3个数)的组合

<2>边界条件,当给被赋予能量的分身数量==给出的分身数量 且剩余能量为0

<3>剪枝,当枚举过程中,被赋予能量的分身数量大于给定的分身数量

#include<iostream>
using namespace std;
int m, n;
int res;
// t:剩余的能量  start:下一个分身的值  state:已经完成的分身
void dfs(int t, int start, int state)
{
	if (state == n && t == 0)//已经完成的分身数量==n  且剩余的能量为0 即返回
	{
		res++;//记录数量
	}
    //可不写“==”
	if (state >= n) return;//已经完成的分身数量>=n,也返回(就算还有剩余能量)

	for (int i = start; i <= t; i++)
	{
		dfs(t - i, i, state + 1);//剩余的能量-i,下一个从i开始枚举,分身的数量++
	}
}

int main()
{
	int T;
	cin >> T;
	while (T--)
	{
		cin >> m >> n;
		res = 0;
		dfs(m, 0, 0);//初始剩余的能量为:m  
		cout << res << endl;
	}

	return 0;
}

 DP(完全背包)

有没有一种可能这个是完全背包,即每个数可以被放入背包无限次

(1)dp数组的含义:总和为i,划分为j个数的所有情况

(2)dp数组的属性:符合题目条件的次数

(3)dp数组的递推公式:观察上面给出的例子可知:

对于最后一步有:划分为j个数情况:

<1>当i<j时,也就是必然有影子被分为0的情况

划分为j个数的情况==划分j-1个数的情况+最后一列放0的情况

<2>当i>=j时,有两种情况:

当最小的数为不为0的情况,那么方案数==总和i-j个1的情况+最小的数为0的情况

·······1:有影子被分到的最小能量为0(最小的数为0的情况被<1>处理了)

·······2:影子的被分到的最小能量不为0

(4)dp数组的初始化:由递推公式可知:dp[i][0]的情况都是1

#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 11;

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        int n, m;
        scanf("%d%d", &m, &n);

        int f[N][N] = {0};//dp数组的含义:总和为i,划分为j个数的所有情况
        f[0][0] = 1;//当总数为0,划分为0个,情况数为:1
        /*问题:
        for(int i=0;i<m;i++) 
            f[i][0]=1;*/
            
        for (int i = 0; i <= m; i ++ )
            for (int j = 1; j <= n; j ++ )
            {
                f[i][j] = f[i][j - 1];//划分为j个数的情况==划分j-1个数的情况+最后一列放0的情况
                if (i >= j) f[i][j] += f[i - j][j];//当最小的数为不为0的情况,那么方案数==总和i-j个1的情况+最小的数为0的情况
            }

        printf("%d\n", f[m][n]);
    }

    return 0;
}

思维解法递归解法

转换为:n个苹果放m个盘的经典问题

#include<cstdio>
#include<algorithm>
#include<iostream>

using namespace std;

int f(int x,
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值