动态规划(六)

动态规划(DAY6)

从今天开始,爷就要写博客了!
之前没写嘛…是因为不(tai)想(ruo)写(le)
不多说,开始!

问题A石子合并I

题目描述

在一直线上摆放 N 堆石子(N≤100),现要将石子有次序地合并成一堆。规定每次只能选相邻的两堆合并成一堆,并将新的一堆的石子数,记为该次合并的费用
例如: N = 6,从左到右每堆石子数为 6 2 3 7 1 2。
下面是一种合并方案:
第1轮:合并3和7,得分10。变为5堆 6 2 10 1 2
第2轮:合并6和2,得分8。变为4堆 8 10 1 2
第3轮:合并8和10,得分18。变为3堆 18 1 2
第4轮:合并10和12,得分22。变为2堆 8 22
第5轮:合并8和22,得分30。变为1堆 30
总费用:10+8+18+22+30 = 88

编一程序,读入堆数一堆石子数,选择一种合并石子的方案,使得做 N-1 次合并,费用的总和最小

输入

一共N+1行,第一行为一个正整数N(2<=N<=100);
以下N行,每行一个正整数,小于10000,分别表示第i堆石子的个数(1<=i<=N)

输出

一个正整数,即最小费用.

样例输入

5
9
12
8
20
5

样例输出

128

这是一道最最最最最经典的DP之一.
首先是一个n,表示有n堆石子,所以我们用a[i]来表示每堆石子的数量.
然后,我们用 f ( i , j ) f(i,j) 来表示区间i-j的最小费用,然后用 a ( i ) a(i) 来表示1-i的石子数量和
那么可以发现, f ( i , j ) = m i n ( f ( i , j ) , f ( i , k ) + f ( k + 1 , j ) + a ( j ) a ( i 1 ) ) f(i,j)=min(f(i,j),f(i,k)+f(k+1,j)+a(j)-a(i-1))
那么,我们只需要一个一个的枚举i,j,k就行了
其余解释,都在代码里了
程序如下:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[40005];
int f[40005][40005]; //最小值
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i]; //先输入每堆石子的数量
        a[i]+=a[i-1]; //前缀和
    }
    memset(f,0x7f7f7f,sizeof(f)); //因为要用min,所以需要初始化为一个很大的值。
    for(int i=1;i<=n;i++) f[i][i]=0; //f(i,i)没有合并,费用为0。
    for(int lenth=2;lenth<=n;lenth++){ //i必然小于j
        for(int i=1,j=lenth;j<=n;i++,j++) //枚举i,j
            for(int k=i;k<=j;k++) //枚举k
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[j]-a[i-1]); //公式
    }
    cout<<f[1][n]; //输出最小值。
    return 0; //完结撒花
}

问题B 救灾

题目描述

为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。请问:你用有限的资金最多能采购多少公斤粮食呢?

输入

输入数据首先包含一个正整数C,表示有C(C<=10)组测试数据,每组测试数据的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

输出

对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个数据的输出占一行。

样例输入

1
8 2
2 100 4
4 100 2

样例输出

400

我们来看一下题目:n是资金,m是种类,p是价格,h是重量,c是袋数
然后求最多重量
emmm…
其实这个题就是背包问题嘛…
n其实就是背包容量,m就是种类,p就是体积,h是价值,c是每种金子的数量…
嗯,到这里你应该已经发现了,这就是一题多重背包
而且,再回头看一下数据,你还会发现,这是一道非常水的多重背包,c<=20!
也就是说呢,我们可以直接用01背包来做,多打一层循环就行了
代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int C;
int p[205],h[205],c[205];
int f[205];
int main(){
    cin>>C;
    while(C--){
        for(int i=1;i<=200;i++) f[i]=0;
        cin>>n>>m;
        for(int i=1;i<=m;i++) cin>>p[i]>>h[i]>>c[i];
        for(int i=1;i<=m;i++)
            for(int l=1;l<=c[i];l++)
                for(int j=n;j>=p[i];j--)
                    f[j]=max(f[j],f[j-p[i]]+h[i]);
        cout<<f[n]<<endl;
    }
    return 0;
} 

那么,有没有更好的方法呢?
当然是有的,不过嘛
我不会
所以继续吧!

扫描二维码关注公众号,回复: 11437058 查看本文章

问题C 光盘

题目描述

有N张光盘,每张光盘有一个价钱,现在要从N张光盘中买M张,预算为L,每张光盘有一个快乐值,要求在不超过预算并且恰好买M张,使得快乐值总和最大。

输入

第一行为一个正整数T(1<=T<=5)表示测试数据个数
每组测试数据第一行为三个正整数N(N<=100),M(M<=N),L(L<=1000)
接下来的N行每行有两个正整数,分别是光盘的价钱与快乐值。

输出

每组数据对应的最大快乐值总和(保证小于2^31)。若无解则输出0.

样例输入

1
3 2 10
11 100
1 2
9 1

样例输出

3

还是先分析一下题目,N其实就是数量,L就是容量,快乐值就是价值,那么这一题就可以看成01背包。
但是,与经典的01背包不同的是,这里规定了:要恰好买M张。
所以,我们要在01背包的基础上改一下
众所周知,01背包的状态转移方程是:
f [ i ] = m a x ( f [ i w [ i ] ] + v [ i ] , f [ i ] ) f[i]=max(f[i-w[i]]+v[i],f[i])
那么多了一个个数的限定,方程又应该是怎样的呢?
我们可以这样想:用 f [ i ] [ j ] f[i][j] 来表示有i个物品,容量为j时的最大快乐值,就可以得出公式:
f [ i ] [ j ] = m a x ( f [ i 1 ] [ j w [ i ] ] , f [ i ] [ j ] ) f[i][j]=max(f[i-1][j-w[i]],f[i][j])
那么我们就能得出如下的程序:

#include<bits/stdc++.h>
using namespace std;
int T;
int n,m,L;
int f[2005][2005];
int p[2005],v[2005];
int main(){
    scanf("%d",&T); 
    while(T--){
        memset(f,0,sizeof(f));
        scanf("%d%d%d",&n,&m,&L);
        for(int i=1;i<=n;i++)scanf("%d%d",&p[i],&v[i]);
        for(int k=1;k<=n;k++)
            for(int i=m;i>=1;i--)
                for(int j=L;j>=p[k];j--)
                    f[i][j]=max(f[i][j],f[i-1][j-p[k]]+v[k]);
        printf("%d\n",f[m][L]);
    }
    return 0;
}

但是
如果给你这样一组数据呢?

1
5 4 15
6 10
7 3
7 4
8 8
6 5

我们会发现,这个应该输出0,因为任意四张光盘的价钱之和都大于预算15.
但为什么会这样呢?
因为我们初始化成了0,所以 我们任意取一个价钱,会发现
f [ 4 ] [ 15 ] = m a x ( f [ 3 ] [ 9 ] + 10 , f [ 4 ] [ 15 ] ) f[4][15]=max(f[3][9]+10,f[4][15])
那么这时我们会发现,f[4][15]一定会等于10,但是其实没有任何方案可以构成f[3][9],所以,在动态转移方程之前,我们还要加一个判断:

if(f[i][j])

你以为这就没了?
不不不,我们还会发现一个东西:
f [ 1 ] [ j ] f[1][j] 永远等于0;
所以我们还需要一个判断

if(i==1)

两个判断合起来,就可以A掉这题了

完整代码:

#include<bits/stdc++.h>
using namespace std;
int T;
int n,m,L;
int f[2005][2005];
int p[2005],v[2005];
int main(){
    scanf("%d",&T); 
    while(T--){
        memset(f,0,sizeof(f));
        scanf("%d%d%d",&n,&m,&L);
        for(int i=1;i<=n;i++)scanf("%d%d",&p[i],&v[i]);
        for(int k=1;k<=n;k++)
            for(int i=m;i>=1;i--)
                for(int j=L;j>=p[k];j--)
                    if(f[i][j] || i==1)f[i][j]=max(f[i][j],f[i-1][j-p[k]]+v[k]);
        printf("%d\n",f[m][L]);
    }
    return 0;
}

问题D 数字

题目描述

有n个数字(0到99)排成一行,每一次可以将相邻的两个数字相加并对100取模(即除以100的余数),将结果取代之前的两个数,一次操作的花费为两个数字相乘。经过n-1次操作后剩下一个数,问剩下一个数时总花费的最小值。

输入

有若干组数据,每组数据第一行为一个正整数n(n<=100)表示数字的个数。
第二行为n个正整数(0到99)

输出

每组数据对应的最小花费。

样例输入

2
18 19
3
40 60 20

样例输出

342
2400

这题一看,那不就是石子合并吗?但是,我们会发现一些不一样的地方:

  • 有若干组数据,没有给变量
  • 相加取模
  • 花费为两个数字相乘
    关于没有给有几组数据这个呢,有一个小东西可以帮我们
while(~scanf("%d",&n)

没错就是这个~,意为只要n不是最后一个就进行循环(应该是吧。。。)
然后后面两个就改一下就好
代码:

#include<bits/stdc++.h>
using namespace std;
long long n;
long long a[105],s[105][105],f[105][105];
const int oo=12345678;
int main(){
   while(~scanf("%d",&n)){
       memset(f,oo,sizeof(f));
       for(int i=1;i<=n;i++){
           scanf("%d",&a[i]);
           f[i][i]=0;
           s[i][i]=a[i];
       }
       for(int lenth=1;lenth<=n;lenth++){
           for(int i=1,j=lenth;j<=n;i++,j++)
               for(int k=i;k<=j;k++)
                   if(f[i][j]+s[i][j]>(f[i][k]+f[k+1][j]+s[i][k]*s[k+1][j])){
                       f[i][j]=f[i][k]+f[k+1][j]+s[i][k]*s[k+1][j];
                       s[i][j]=(s[i][k]+s[k+1][j])%100;
                   }
       }
       cout<<f[1][n]<<endl;
   }
   return 0;
} 

这篇博客,就结束了。

猜你喜欢

转载自blog.csdn.net/LOSER_LU/article/details/106769361