背包问题 动态规划 矩阵链乘

##备注:这次有六道题目,所以分成三次来写
第二次:https://blog.csdn.net/lil_junko/article/details/92759977
第三次:https://blog.csdn.net/lil_junko/article/details/95269014

题目(1)

给定一个承重量为C的背包,n个重量分别为w​1​​,w​2​​,…,w​n​​的物品,物品i放入背包能产生p​i​​(>0)的价值(i=1,2,…,n)。 每个物品要么整个放入背包,要么不放。要求找出最大价值的装包方案。
输入格式:输入的第一行包含两个正整数n和C(1≤n≤20),第二行含n个正整数分别表示n个物品的重量,第三行含n个正整数分别表示n个物品放入背包能产生的价值。
输出格式:在一行内输出结果,包括最大价值装包方案的价值、具体装包方案,用空格隔开。具体装包方案是n个物品的一个子集,用长度为n的0、1串表示(1表示对应物品被选中,0表示没有被选中)。如果这样的0、1串不唯一,取字典序最大的那个串。
输入样例:
4 9
2 3 4 5
3 4 5 7
输出样例:
12 1110
(注:1110 和0011都是价值最大的装包方案,取字典序最大的结果即为1110)

简单分析

1.典型的背包问题:用动态规划的思想,一个物品数量为长度,背包容量为宽度的数组来储存每次分步的解。(详情可见课本P139)
KNAPSACK:
input: Item Collection U = {u1, u2, …, un}, Item volume s1, s2, …, sn, Item value v1, v2, …, vn , knapsack volume C.
Output: Maxinum value of items which a backpack can hold
for i ← 0 to n
------V[i,0] ← 0
end for
for j ← 0 to C
V[0,j] ← 0
end for
for i ← 1 to n
------for j ← 1 to C
------------V[i,j] ← V[i-1,j]
------------if si ≤ j then V[i,j] ← max{V[i,j], V[i-1, j-si] + vi}
------end for
end for
return V[n,C]
(注:横杠代表缩进)
2.这里要记录字典序最大的串,又因为物品数量n小于32,所以可以用一个int来记录他的路径,当int的第n位为零代表不在背包内,反之代表在背包内,这样相对于用一个数组来记录路径更加节省内存和方便比较字典序大小。

代码

#include 
#include 
#include 
using namespace std;
int main()
{
    //N记录物品数量,C记录背包容量,volume记录物品体积,value记录物品价值
    //all_value记录在各个情况下的最大质量,sign用于记录装包方式
    int N, C;
    int volume[100], value[100];
    int all_value[100][1000], sign[100][1000];
    //输入数据,并将所有数组初始化
    cin >> N >> C;
    for(int tmp = 1; tmp <= N; tmp++)
        cin >> volume[tmp];
    for(int tmp = 1; tmp <= N; tmp++)
        cin >> value[tmp];
    for(int tmp = 0; tmp <= N; tmp++)
        memset(sign[tmp], 0, sizeof(sign[tmp]));
    for(int tmp = 0; tmp <= N; tmp++)
        all_value[tmp][0] = 0;
    for(int tmp = 0; tmp < C; tmp++)
        all_value[0][tmp] = 0;
    //逐步遍历数组all_sign    
    for(int tmp1 = 1; tmp1 <= N; tmp1++)
        for(int tmp2 = 1; tmp2 <= C; tmp2++)
        {
            //取V[i-1,j]和V[i-1, j-Si] + Vi较大者放入V[i,j]
            all_value[tmp1][tmp2] = all_value[tmp1 - 1][tmp2];
            sign[tmp1][tmp2] = sign[tmp1 - 1][tmp2];
            if(volume[tmp1] <= tmp2)
                //若相等则取路径字典序较大的那条
                if(all_value[tmp1 - 1][tmp2 - volume[tmp1]] + value[tmp1] > all_value[tmp1][tmp2])
                {
                    all_value[tmp1][tmp2] = all_value[tmp1 - 1][tmp2 - volume[tmp1]] + value[tmp1];
                    sign[tmp1][tmp2] = sign[tmp1 - 1][tmp2 - volume[tmp1]] + pow(2, tmp1-1);
                }
        }
    //输入最大值和路径
    cout << all_value[N][C] << " ";
    int tmp_num = 1;
    for(int tmp = 1; tmp <= N; tmp++)
    {
        cout << (sign[N][C] % (2*tmp_num)) / tmp_num;
        tmp_num *= 2;
    }
}

代码(2)

用动态规划法确定n个矩阵链乘M​1​​M​2​​…M​n​​运算量最小(即数量乘法次数最少)的计算次序.。
输入格式:输入的第一行包含一个整数n(1≤n≤20),接下来一行是n+1个数依次是n个矩阵的行数以及最后一个矩阵的列数。注意,根据矩阵乘法定义,两个矩阵相乘M​i​​M​i+1​​意味着后一矩阵M​i+1​​的行数与前一矩阵M​i​​的列数相同。
输出格式: 在一行内输出这n个矩阵链乘时数量乘法的最少次数,以及一种对应的最优计算次序。最优计算次序采用对M​1​​M​2​​…M​n​​加括号的形式输出,一共n−1对括号(参见输出样例)。 如果有多种最优计算次序,则取每层括号尽量靠前的那种次序(即每层括号划分的两个矩阵子序列中前面子序列尽可能短的那种)。例如,如果M​1​​M​2​​M​3​​M​4​​M​5​​的最优计算次序的最外层括号有两种方式:(M​1​​M​2​​M​3​​)(M​4​​M​5​​)和(M​1​​M​2​​)(M​3​​M​4​​M​5​​), 则最外层括号取后一种,因为其括号更靠前。
输入样例:
5
5 10 4 6 10 2
输出样例:
348 (M1(M2(M3(M4M5))))

简单分析

1.首先矩阵链乘的基础知识:A×B的乘法次数为 Rowa × Rowb × Columnb (注:两个矩阵能相乘必定存在Columna = Rowb
2.采用动态规划的思想,每次将计算结果打表,然后每次只用计算出row[i] * col[k] * col[j]这一步计算出来再与原有两个矩阵之前的计算量相加得到结果。
3.用另外的一个数组记录路径,如果在同样大小的情况下,优先记录括号靠前的那种(就是字典序较小的那条)

代码

#include
#include
#define N 100
#define Max 65535
using namespace std;
//row矩阵的行数,col记录矩阵的列数
//data记录各个情况下乘法最小次数,line记录括号位置
int row[N], col[N];
int data[N][N];
int line[N][N];
//递归输出路径
void answer(int i,int j)
{
    if(i==j)
        cout<<"M"<>n;
    int i =1;
    cin >> row[1];
    while(i < n)
    {
        i++;
        cin >> row[i];
        col[i - 1] = row[i];
    }
    cin >> col[n];
    //l代表每次链乘的举证数量,j代表每次相乘的首位置,k代表他分割的位置
    int l,j,k;
    for(l=2;l<=n;l++)
    {
        for(i=1;i<=n-l+1;i++)
        {
            j = i+l-1;
            data[i][j] = Max;
            int q =0;
            for(k=i;k<=j-1;k++)
            {
                q = data[i][k] + data[k+1][j] + row[i] * col[k] * col[j];
                //相等情况下记录括号只靠前的那种
                if(data[i][j]>q)
                {
                    data[i][j] = q;
                    line[i][j] = k;
                }
            }
        }
    }
    //输出数据和路径
    cout << data[1][n] << " ";
    answer(1,n);
    return 0;
}


你可能感兴趣的:(C++)