当前位置: 首页 > article >正文

算法-动态规划三

切蛋糕


描述
有一块矩形蛋糕,宽和高分别是整数w、h。现要将其切成m块小蛋糕,每个小蛋糕都必须是矩形、且宽和高均为整数。切蛋糕时,每次切一块蛋糕,将其分成两个矩形蛋糕。请计算:最后得到的m块蛋糕中,最大的那块蛋糕的面积下限。


输入
共有多行,每行表示一个测试案例。

每行是三个用空格分开的整数w, h, m ,其中1 ≤ w, h, m ≤ 20 , m ≤ wh.

当 w = h = m = 0 时不需要处理,表示输入结束。

输出
每个测试案例的结果占一行,输出一个整数,表示最大蛋糕块的面积下限。

解题思路

设ways(w,h,m)表示宽为w,高为h的蛋糕,被切m刀后,最大的那块蛋糕的面积最小 值

题目就是要求ways(W,H,M-1) 边界条件:

w * h < m + 1  INF

m == 0  w*h

递推式: SV为第一刀竖着切时能得到的最好结果,SH为第一刀横着切时能得到的最好结 果,则ways(w,h,m) = min(SV,SH) 

SV = min{ Si, i = 1 ... w-1 }, 其中: Si = 为第一刀左边宽为i的情况下的最好结果 

Si = min{ max(ways(i,h,k),ways(w-i,h,m-1-k)), k = 0... m-1 }

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int inf=0x3f3f3f3f; // 定义一个无穷大的常量
int dp[405][25][25]; // 定义一个三维数组,用于存储动态规划的结果
int dfs(int m,int w,int h) // 定义一个递归函数,用于计算最大面积
{
    if(dp[m][w][h]!=inf) // 如果dp[m][w][h]已经计算过,则直接返回
        return dp[m][w][h];
    else if(m==0) // 如果m为0,则返回w*h
        return dp[m][w][h]=w*h;
    else
    {
        int ans=inf; // 初始化ans为无穷大
        for(int i=1,lim=w/2+1;i<=lim;i++) // 遍历所有可能的宽度
            for(int k=0;k<=m-1;k++) // 遍历所有可能的层数
                ans=min(ans,max(dfs(k,i,h),dfs(m-1-k,w-i,h))); // 计算最大面积
        for(int i=1,lim=h/2+1;i<=lim;i++) // 遍历所有可能的高度
            for(int k=0;k<=m-1;k++) // 遍历所有可能的层数
                ans=min(ans,max(dfs(k,w,i),dfs(m-1-k,w,h-i))); // 计算最大面积
        return dp[m][w][h]=ans; // 返回最大面积
    }
}
int main()
{
    int w,h,m; // 定义宽度、高度和层数
    memset(dp,0x3f,sizeof(dp)); // 将dp数组初始化为无穷大
    while(cin>>w>>h>>m && w||h||m) // 循环输入宽度、高度和层数,直到输入为0
        cout<<dfs(m,w,h)<<endl; // 输出最大面积
    return 0;
}

4 4 4
4
4 4 3
6

灌溉草场

在一片草场上:有一条长度为L (1 <= L <= 1,000,000,L为偶数)的线段 。John的N (1 <= N <= 1000) 头奶牛都沿着草场上这条线段吃草,每头牛的活动范围是一个开区间(S,E),S,E都是整数。不同奶牛的活动范围可以有重叠。

John要在这条线段上安装喷水头灌溉草场。每个喷水头的喷洒半径可以随意调节,调节范围是[A B ](1 <= A <= B <= 1000),A,B都是整数。要求 线段上的每个整点恰好位于一个喷水头的喷洒范围内 每头奶牛的活动范围要位于一个喷水头的喷洒范围内 任何喷水头的喷洒范围不可越过线段的两端(左端是0,右端是L )

请问,John 最少需要安装多少个喷水头。

输入

        第1行:整数N、L。 第2行:整数A、B。 第3到N+2行:每行两个整数S、E (0 <= S < E <= L) ,表示某头牛活动 范围的起点和终点在线段上的坐标(即到线段起点的距离)。

输出:

        最少需要安装的多少个喷水头;若没有符合要求的喷水头安装方案 ,则输出-1。

从线段的起点向终点安装喷水头,令f(X)表示:所安装喷水头的喷洒范围恰好覆盖直线上的区间[0 X]时,最少需要多少个喷水头

显然,X应满足下列条件

  •  X为偶数
  •  X所在位置不会出现奶牛,即X不属于任何一个(S,E)
  •  X>=2A
  • 当X>2B时,存在Y属于[X-2B X-2A]且Y满足上述三个条件,使得 f(X)=f(Y)+1

 递推计算f(X)

  1.  f(X) = ∝ : X 是奇数
  2.  f(X) = ∝ : X < 2A  f(X) = ∝ :X处可能有奶牛出没
  3.  f(X)=1: 2A<=X<=2B 、且X位于任何奶牛的活动范围之外
  4.  f(X)=1+min{f(Y): Y[X-2B X-2A]、Y位于任何奶牛的活动范围 之外}:X>2B,f(X)=1+min{f(Y): Y属于[X-2B X-2A]、Y位于任何奶牛的活动范围之 外}:X>2B
  5. 对每个X求f(X),都要遍历区间[X-2B, X -2A]去寻找其中最小的 f(Y),则时间复杂度为:L * B = 1000000 * 1000,太慢。
  6.  快速找到[X-2B X-2A]中使得f(Y)最小的元素是问题求解速度的关键。
  7. 可以使用优先队列priority_queue! (multiset也可以,比 priority_queue慢一点)!
  8.  求F(X)时,若坐标属于[X-2B, X-2A]的二元组(i,F(i))都保存在一 个priority_queue中,并根据F(i)值排序,则队头的元素就能确保 是F(i)值最小的。
  9. 在求X点的F(x)时,必须确保队列中包含所有属于[X-2B,X-2A]的点。 而且,不允许出现坐标大于X-2A的点,因为这样的点对求F(X)无用, 如果这样的点出现在队头,因其对求后续点的F值有用,故不能抛弃之 ,于是算法就无法继续了。
  10.  队列中可以出现坐标小于X-2B 的点。这样的点若出现在队头,则直接将其抛弃。 
  11. 求出X点的F值后,将(X-2A+2, F(X-2A+2))放入队列,为求F(X+2)作 准备
  12.  队列里只要存坐标为偶数的点即可。
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int INFINITE = 1 << 31; //表示无解
const int MAXL = 1000010; //线段最大值,数值设大点是为了防止数据越界
const int MAXN = 1010; //奶牛数最大值
//状态:f(X)表示:所安装喷水头的喷洒范围恰好覆盖直线上的区间[0 X]时,最少需要多少个喷水头
int F[MAXL];
//cowThere[i]==1表示第i点有奶牛
int cowThere[MAXL];
int N, L, A, B;

struct Fx
{
    int x;
    int f;

    Fx(int xx = 0, int ff = 0) : x(xx), f(ff)
    {
    }

    bool operator <(const Fx& a) const
    {
        return f > a.f;
    }
};

//Fx.f值升序排列
priority_queue<Fx> qFx;

int main()
{
//  freopen("in.txt", "r", stdin);
    cin >> N >> L >> A >> B;

    memset(cowThere, 0, sizeof(cowThere));
    for (int i = 0; i < N; i++) {
        int s, e;
        cin >> s >> e;
        //奶牛活动区间:(s, e),用cowThere记录奶牛区间临界点
        ++cowThere[s + 1];
        --cowThere[e];
    }
    //输入数据为奶牛活动的半径,改为直径值,A、B都乘2后必为偶数,所以每次探索的右区间也为偶数
    A <<= 1;
    B <<= 1;

    //计算cowThere真正的值
    int inCows = 0; //当前点在多少头奶牛的活动范围之内
    for (int i = 0; i <= L; i++) {
        F[i] = INFINITE;
        inCows += cowThere[i];
        cowThere[i] = inCows > 0; //区间有进有出,类比(),用累加方式可能是否在区间内
    }
    //边界条件:f(X)=1: 2A<=X<=2B,且X位于任何奶牛的活动范围之外
    for (int i = A; i <= B; i += 2) {
        if (!cowThere[i]) {
            F[i] = 1;
            //下面求f(B+2),不允许出现坐标大于X-2A的点
            if (i <= B + 2 - A) {
                qFx.push(Fx(i, 1));
            }
        }
    }
    //从f(B+2)一直计算到f(L), f(L)即为结果值
    for (int i = B + 2; i <= L; i += 2) {
        if (!cowThere[i]) {
            Fx fx;
            while (!qFx.empty()) {
                fx = qFx.top();
                //小于X-B的点抛弃,对f(X)而言,求f(X)=min(f(Y)) + 1, Y在[Y-B, Y-A]之间
                if (fx.x < i - B) {
                    qFx.pop();
                }
                else {
                    break;
                }
            }
            if (!qFx.empty()) {
                //状态转移方程:f(X)=min(f(Y)) + 1
                F[i] = fx.f + 1;
            }
        }

        //下一个点是X=i+2,加入下一个点所需f(Y),Y在[i+2-B, i+2-A]之间
        if (F[i - A + 2] != INFINITE) {
            qFx.push(Fx(i - A + 2, F[i - A + 2]));
        }
    }
    if (F[L] == INFINITE) {
        cout << -1 << endl;
    }
    else {
        cout << F[L] << endl;
    }
    return 0;
}

2 8 
1 2 
6 7 
3 6
3

方盒游戏

给定游戏开始时的状态,玩家可获得的最高积分是多少?

输入:第一行是一个整数t(1<=t<=15),表示共有多少组测试数据。每组测试 数据包括两行

  • 第一行是一个整数n(1<=n<=200),,表示共有多少个小块
  • 第二行包括n个整数,表示每个小块的颜色。这些整数的取值范围是[1 n]

输出:对每组测试数据,分别输出该组测试数据的序号、以及玩家可以获得 的最高积分。

ClickBox(i,j,ex_len) 表示: 大块j的右边已经有一个长度为ex_len的大块(该大块可能是在合并过程中形成的,不妨就称其为ex_len),且j的颜色和ex_len相 同,在此情况下将i到j以及ex_len都消除所能得到的最高分。

于是整个问题就是求:ClickBox(0,n-1,0)

求ClickBox(i,j,ex_len)时,有两种处理方法,取最优者 假设j和ex_len合并后的大块称作Q

  • 将Q直接消除,这种做法能得到的最高分就是: ClickBox(i,j-1,0) + (len[j]+ex_len)2
  •  期待Q以后能和左边的某个同色大块合并。需要枚举可能和Q 合并的大块。假设让大块k和Q合并,则此时能得到的最大 分数是: ClickBox(i,k,len[j]+ex_len) + ClickBox(k+1,j-1,0)

递归的终止条件:ClickBox(i,j,ex_len) 递归的终止条件: i == j

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define N 205
using namespace std;
int f[N][N][N],n,sum[N],a[N];
int main(){
    // 定义一个二维数组f,用于存储每个子串的最大平方和
    // 定义一个数组sum,用于存储每个元素出现的次数
    // 定义一个数组a,用于存储输入的字符串
    int t;cin>>t;int num=t;
    // 定义一个变量t,用于存储输入的测试用例数
    // 定义一个变量num,用于存储测试用例的编号
    while(t--){
        // 循环t次,每次处理一个测试用例
        scanf("%d",&n);
        // 输入字符串的长度
        memset(sum,0,sizeof(sum));
        // 将sum数组初始化为0
        memset(f,0,sizeof(f));
        // 将f数组初始化为0
        for(int i=1;i<=n;++i)scanf("%d",&a[i]);
        // 输入字符串
        for(int i=1;i<=n;++i){
            for(int j=i+1;j<=n;++j){
                // 遍历字符串,统计每个元素出现的次数
                if(a[i]==a[j])sum[i]++;
            }
        }
        // 遍历字符串,统计每个元素出现的次数
        for(int i=n;i;--i){
            for(int j=i;j<=n;++j){
                for(int k=0;k<=sum[j];++k){
                    // 遍历每个子串,计算每个子串的最大平方和
                    f[i][j][k]=max(f[i][j][k],f[i][j-1][0]+(k+1)*(k+1));
                    // 如果当前子串的最后一个元素与前面的元素相等,则更新最大平方和
                    for(int p=i;p<j;++p){
                        if(a[p]==a[j]){
                            f[i][j][k]=max(f[i][j][k],f[i][p][k+1]+f[p+1][j-1][0]);
                        }
                    }
                }
            }
        }
        // 遍历每个子串,计算每个子串的最大平方和
        printf("Case %d: %d\n",num-t,f[1][n][0]);
        // 输出结果
    }
    return 0;
}



1 2 2 2 2 3 3 3 1
Case 1: 29
1
1
Case 2: 1


http://www.kler.cn/a/611603.html

相关文章:

  • 学习threejs,使用Sprite精灵、SpriteMaterial精灵材质
  • 企业内训|DeepSeek技术革命、算力范式重构与场景落地洞察-某头部券商
  • 精选10个好用的WordPress免费主题
  • ELK stack基础架构
  • Android TextView实现跑马灯效果性能优化
  • 用shell脚本,批量备份MySQL中所有数据库,并批量还原!
  • asp.net进销存软件WEB进销存ERP软件库存玻璃行业
  • SQLite优化实践
  • 【设计模式】策略模式(Strategy Pattern)详解
  • 5分钟快速上手Docker容器化部署:从零到实践
  • 带你刷题—公因子的数目(leetcode2427)
  • docker-操作实战
  • Visual Studio 使用 IntelliCode AI 辅助代码开发
  • 【CUDA】mnist_cuda
  • 模块学习篇#2:解析常用于YOLO等深度学习模型的注意力机制CBAM
  • Oracle常用分析诊断工具(9)——ADDM
  • Java单例设计模式详解
  • 深度学习篇---卷积网络结构
  • 【CodeReview】Jupiter(Eclipse插件)代码审查工具简介
  • Oracle补丁自动化安装步骤