【蓝桥杯】每日练习 Day21

目录

前言

背包

货币问题

分析

代码

砝码称重

分析

代码

区间dp

石子合并

分析

代码

游戏

分析

代码


前言

今天依旧不讲数论 ,明天我会把数论剩下的组合数求法容斥原理 补上,今天带来的是背包 和**区间dp**的超级简单题总共四道。


背包

先来讲一下什么是背包问题 ,背包问题又被称为有限制的选法问题 ,注意这里的选法是没有顺序 要求的。按照这个思路,如果我们发现一道dp问题没有对顺序的要求 ,那么就可以思考 一下问题是不是某种背包模型了。

但是没有顺序的要求并不是 说我们可以随便选,随便选的话很容易 就会出现某个元素选的个数超过要求 的情况,为了避免 这种错误呢,背包问题一般都用i个物品的所有选法 作为划分 ,每次只在原来的基础上增加一种物品 的选法,这样就可以很好的控制每种物品选择的数量,巧妙的避开了乱选的情况。

而又因为每种物品可能不止一个 ,所以我们又需要一维来枚举 这个物品选择的个数 ,那么状态表示就变为了**dp[i][j]** ,一般情况下背包问题 都是二维 的,少数情况可以 优化到一维 ------参考**01背包** 和完全背包 这两种。(不会背包模板的同学请先移步至背包九讲专题_哔哩哔哩_bilibili

讲完了背包问题的大致思路以后我们就来写两道题练练手。


货币问题


分析

可以发现题目与选择的顺序无关 ,只与选择的方案有关 ,所以我们就可以尝试能否 通过背包 来求解,发现对于每种货币都没有选择限制,所以考虑完全背包

那么问题就转化成了完全背包求方案数 ,直接套模板就好。(注意这里的方案数空集要初始化为1 ------求方案数的dp问题 一般都需要初始化空集,大家记住就好)


代码

cpp 复制代码
//完全背包
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 10010;
int n, v;
LL dp[N]; 
int nums[N];
int main()
{
    scanf("%d%d", &n, &v);
    for(int i = 0; i < n; i++)
        scanf("%d", nums + i);
    dp[0] = 1;
    for(int i = 0; i < n; i++)
        for(int j = nums[i]; j <= v; j++)
            dp[j] += dp[j - nums[i]];
    printf("%lld", dp[v]);
    return 0;
}

砝码称重


分析

发现每种砝码可以分为选或不选 两种情况,而且本题与选择的顺序无关 只与选择的方案有关 所以考虑**01背包** 。(也可以说是状态机dp

每种砝码选择的话需要分为两种情况 ------放左边或放右边

显然因为左右只是相对的 ,所以我们的所有方案也可以分为两种情况 (分别是正数域和负数域 ),对于这道题我们只考虑正数域

不过这里有一个小细节 ,就是当枚举的重量小于当前砝码的重量时可能会变成负数相对的 一种选法在负数域时遇到这种情况也有可能会变成正数

也可以这样考虑------所有结果为正数的情况只会由两种情况变化而来 ,即:正数 变成正数负数 变成正数

因为我们要考虑所有的正数 的情况所以这种方案也要记录,具体操作就是取abs


代码

cpp 复制代码
/*
    每个砝码都三种状态:放左边,右边和不放,显然不放不会改变方案的总数,所以不考虑
    总量不会超过1e5,所以打表
*/
#include<iostream>
using namespace std;
const int N = 110, M = 1e5 + 10;
int n;
bool dp[N][M];
int nums[N];
​
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", nums + i);
    dp[0][0] = true;
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= 1e5 - nums[i]; j++)
            if(dp[i - 1][j]) //只用上一层的可以
            {
                dp[i][j] = true; 
                dp[i][abs(j - nums[i])] = true;
                dp[i][j + nums[i]] = true;
            }
    int l = 0;
    for(int i = 1; i < M; i++)
        l += dp[n][i];
    printf("%d", l);
    
    return 0;
}

区间dp

区间dp 呢,我也没有做多少题,不是很了解。我能发现的区间dp大概可以理解为状态是区间状态的子状态依旧是区间 ,所以对于区间dp问题我们就可以通过求子区间来推导出原区间。


石子合并


分析

区间dp的一道模板题 ,状态表示是**dp[l][r]** ,两维分别代表区间的左端点和右端点

状态转移:dp[l][r] = min(dp[l][k] + dp[k + 1][r]) + s[r] - s[l - 1]

利用分治 的思想将区间划分为两段 ,这样就可以利用子问题的结果求值了(s为前缀和数组)恍惚间觉得这道题好像讲过(

这道题注意一下初始化即可,时间复杂度为O(n^3)


代码

cpp 复制代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 310;
int n;
int nums[N];
int s[N];
int dp[N][N];
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", nums + i);
    memset(dp, 0x3f, sizeof dp);
    for(int i = 1; i <= n; i++)
    {
        s[i] = s[i - 1] + nums[i];
        dp[i][i] = 0;
    }
    
    for(int i = 2; i <= n; i++) //枚举区间长度
    {
        for(int j = 1; j + i - 1 <= n; j++) //枚举左端点
        {
            int l = j, r = j + i - 1;
            for(int k = l; k < r; k++)
                dp[l][r] = min(dp[l][k] + dp[k + 1][r], dp[l][r]);
            dp[l][r] += s[r] - s[l - 1];
        }
    }
    printf("%d", dp[1][n]);
    return 0;
}

游戏


分析

这游戏一点也不好玩QAQ。

说是区间dp但我认为这道题主要是**思维 + 博弈论**。

首先我们发现无论 游戏如何进行AB总分 都是不变 的,而胜利的条件是一方大于另一方 ,可以写为**A > B**。

这里如果我们去枚举的话可以发现**AB** 是两种状态 ,这里有一个常见的小思路 ,就是移项大小关系转化成差值A - B > 0 。(主播依稀记得前面有一道前缀和 + 贪心的题目也是使用这种思路优化。这就不得不提复盘的好处了,不然主播学一点忘一点。)这样优化以后就只需要 考虑一个变量了。

之后就是我们博弈论 的思考方式了,即------最差情况下的最好

什么意思呢?其实就是双方都是绝顶聪明 的,而**01游戏的特点就是一方若是最好** ,那么另一方必然是最差,所以说这个最差是对方造成的,我们需要在这种情况下找出最优方案。

再加上我们前面的思路,我们选择求**A - B的最大值** ,而对于A - B ,可以划分为两个集合 ,一种是选择左端的最佳情况,可以表示为:

a[l] - (B - A) 注意这里的A 和 B只是表示的相对状态并不是数值!

同理,另一端可以表示为**a[r] - (B - A)** ,当前情况最优就是两者的最大值

而求出了**A - B = d** 如何求出AB呢?

前面我们说了01游戏的特点是**A + B = s** ,这是一个恒等式 ,所以接下来我们对两者联立,就可以求出:

A = (s + d) / 2

B = (s - d) / 2

接下来是代码。


代码

cpp 复制代码
#include<iostream>
using namespace std;
const int N = 110;
int a[N];
int n, s;
int dp[N][N];
int dfs(int l, int r)
{
    if(l == r) return a[l];
    if(dp[l][r]) return dp[l][r];
    int x = a[l] - dfs(l + 1, r);
    x = max(a[r] - dfs(l, r - 1), x);
    return dp[l][r] = x;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", a + i);
        s += a[i];
    }
​
    int l = dfs(1, n); // 计算 A - B
    printf("%d %d", (s + l) / 2, (s - l) / 2); //01游戏
    return 0;
}
相关推荐
ideaout技术团队3 小时前
leetcode学习笔记2:多数元素(摩尔投票算法)
学习·算法·leetcode
代码充电宝3 小时前
LeetCode 算法题【简单】283. 移动零
java·算法·leetcode·职场和发展
不枯石5 小时前
Matlab通过GUI实现点云的均值滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab·均值算法
不枯石6 小时前
Matlab通过GUI实现点云的双边(Bilateral)滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab
白水先森7 小时前
C语言作用域与数组详解
java·数据结构·算法
想唱rap8 小时前
直接选择排序、堆排序、冒泡排序
c语言·数据结构·笔记·算法·新浪微博
老葱头蒸鸡9 小时前
(27)APS.NET Core8.0 堆栈原理通俗理解
算法
视睿9 小时前
【C++练习】06.输出100以内的所有素数
开发语言·c++·算法·机器人·无人机
保持低旋律节奏9 小时前
CPP——OJ试题,string、vector、类(题三)初步应用
c++
君生我老10 小时前
C++ string类常用操作
c++