动太规划问题

本文深入探讨了背包问题的几种类型,包括01背包、完全背包、多重背包及子序列问题,如最长公共子序列。通过实例分析,详细阐述了每种问题的解决策略与实现方法,同时介绍了动态规划、逆推法等高效算法,以及解决子序列问题的动态规划与贪心+二分查找策略。文章最后讨论了最小移动次数使数列有序的最长上升子序列问题,提供了多种算法解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

包问题01背包: 有N件物品和一个重量为M的背包。(每种物品均只有一件)第i件物品的重量是w[i],价值是p[i]。求解将哪些物品装入背包可使价值总和最大。完全背包: 有N种物品和一个重量为M的背包,每种物品都有无限件可用。第i种物品的重量是w[i],价值是p[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大。多重背包: 有N种物品和一个重量为M的背包。第i种物品最多有n[i]件可用,每件重量是w[i],价值是p[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大。01背包问题:这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。用子问题定义状态:即c[i][v]表示前i件物品恰放入一个重量为m的背包可以获得的最大价值。则其状态转移方程便是:c[i][m]=max{c[i-1][m],c[i-1][m-w[i]]+p[i]}这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入重量为m的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为c[i-1][m];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的重量为m-w[i]的背包中”,此时能获得的最大价值就是c[i-1][m-w[i]]再加上通过放入第i件物品获得的价值p[i]。测试数据:10,33,44,55,6c[i][j]数组保存了1,2,3号物品依次选择后的最大价值.这个最大价值是怎么得来的呢?从背包容量为0开始,1号物品先试,0,1,2,的容量都不能放.所以置0,背包容量为3则里面放4.这样,这一排背包容量为4,5,6,....10的时候,最佳方案都是放4.假如1号物品放入背包.则再看2号物品.当背包容量为3的时候,最佳方案还是上一排的最价方案c为4.而背包容量为5的时候,则最佳方案为自己的重量5.背包容量为7的时候,很显然是5加上一个值了。加谁??很显然是7-4=3的时候.上一排 c3的最佳方案是4.所以。总的最佳方案是5+4为9.这样.一排一排推下去。最右下放的数据就是最大的价值了。(注意第3排的背包容量为7的时候,最佳方案不是本身的6.而是上一排的9.说明这时候3号物品没有被选.选的是1,2号物品.所以得9.)public class Pack01 {public int [][] pack(int m,int n,int w[],int p[]){//c[i][v]表示前i件物品恰放入一个重量为m的背包可以获得的最大价值int c[][]= new int[n+1][m+1];for(int i = 0;i<n+1;i++)c[i][0]=0;for(int j = 0;j<m+1;j++)c[0][j]=0;//for(int i = 1;i<n+1;i++){for(int j = 1;j<m+1;j++){//当物品为i件重量为j时,如果第i件的重量(w[i-1])小于重量j时,c[i][j]为下列两种情况之一://(1)物品i不放入背包中,所以c[i][j]为c[i-1][j]的值//(2)物品i放入背包中,则背包剩余重量为j-w[i-1],所以c[i][j]为c[i-1][j-w[i-1]]的值加上当前物品i的价值if(w[i-1]<=j){if(c[i-1][j]<(c[i-1][j-w[i-1]]+p[i-1]))c[i][j] = c[i-1][j-w[i-1]]+p[i-1];elsec[i][j] = c[i-1][j];}elsec[i][j] = c[i-1][j];}}return c;}    /**     * 逆推法求出最优解     * @param c     * @param w     * @param m     * @param n     * @return     */    public int[] printPack(int c[][],int w[],int m,int n){                int x[] = new int[n];        //从最后一个状态记录c[n][m]开始逆推        for(int i = n;i>0;i--){            //如果c[i][m]大于c[i-1][m],说明c[i][m]这个最优值中包含了w[i-1](注意这里是i-1,因为c数组长度是n+1)            if(c[i][m]>c[i-1][m]){                x[i-1] = 1;                m-=w[i-1];            }        }        for(int j = 0;j<n;j++)            System.out.println(x[j]);        return x;    }public static void main(String args[]){int m = 10;int n = 3;int w[]={3,4,5};int p[]={4,5,6};Pack01 pack = new Pack01();int c[][] = pack.pack(m, n, w, p);pack.printPack(c, w, m,n);}}5.问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。 求解:引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。问题的递归式写成:回溯输出最长公共子序列过程: 算法分析:由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。 代码: #include #include #define MAXLEN 100void LCSLength(char *x, char *y, int m, int n, int c[][MAXLEN], int b[][MAXLEN]){ int i, j; for(i = 0; i <= m; i++) c[i][0] = 0; for(j = 1; j <= n; j++) c[0][j] = 0; for(i = 1; i<= m; i++) { for(j = 1; j <= n; j++) { if(x[i-1] == y[j-1]) { c[i][j] = c[i-1][j-1] + 1; b[i][j] = 0; } else if(c[i-1][j] >= c[i][j-1]) { c[i][j] = c[i-1][j]; b[i][j] = 1; } else { c[i][j] = c[i][j-1]; b[i][j] = -1; } } }}void PrintLCS(int b[][MAXLEN], char *x, int i, int j){ if(i == 0 || j == 0) return; if(b[i][j] == 0) { PrintLCS(b, x, i-1, j-1); printf("%c ", x[i-1]); } else if(b[i][j] == 1) PrintLCS(b, x, i-1, j); else PrintLCS(b, x, i, j-1);}int main(int argc, char **argv){ char x[MAXLEN] = {"ABCBDAB"}; char y[MAXLEN] = {"BDCABA"}; int b[MAXLEN][MAXLEN]; int c[MAXLEN][MAXLEN]; int m, n; m = strlen(x); n = strlen(y); LCSLength(x, y, m, n, c, b); PrintLCS(b, x, m, n); return 0;}6.给你一个数列,一次操作是指将某个数移到数列中别的位置上去,然后问最少要几次操作才能让数列变得有序。例如,数列7,1,3,2,6,5就只需要三次移动,把3移到2后面,把5移到6前面,再把7移到最后面即可。 问题等价转化:其实就是一个最长上升子序列问题。因为整个操作过程实质上可以等价地看作是,把要移动的数先全部取出来,再挨个放回适当的位置。这就要求取出要移动的数后,剩下的那些数本身是有序的。若希望要移动的数越少越好,那也就等于说是剩下的不动的数要越多越好。 最长上升子序列问题:给出一个由n个数组成的序列x[1..n],找出它的最长单调上升子序列。即求最大的m和a1,a2……,am,使得a1<a2<……<am且x[a1]<x[a2]<……<x[am]。 动态规划求解思路分析:(O(n^2))经典的O(n^2)的动态规划算法,设A[i]表示序列中的第i个数,F[i]表示从1到i这一段中以i结尾的最长上升子序列的长度,初始时设F[i] = 0(i = 1, 2, ..., len(A))。则有动态规划方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。 贪心+二分查找:(O(nlogn)) 开辟一个栈,每次取栈顶元素s和读到的元素a做比较,如果a>s, 则加入栈;如果a<s,则二分查找栈中的比a大的第1个数,并替换。 最后序列长度为栈的长度。 这也是很好理解的,对x和y,如果x<y且E[y]<E[x],用E[x]替换 E[y],此时的最长序列长度没有改变但序列Q的''潜力''增大。 举例:原序列为1,5,8,3,6,7 栈为1,5,8,此时读到3,则用3替换5,得到栈中元素为1,3,8, 再读6,用6替换8,得到1,3,6,再读7,得到最终栈为1,3,6,7 ,最长递增子序列为长度4。 代码:#include using namespace std;//////////////DP:dp[i] = max{1,dp[j]+1} (j=1,2, ...,i-1,且A[j]<A[i])。template int Lis_Dp(T seq[],int n){ int *dp=new int[n]; memset(dp,0,n*sizeof(int)); dp[0]=1; for(int i=1;i<n;i++) { int max=1; for(int j=0;j<i;j++) { if(seq[j]<seq[i]) { if(max<dp[j]+1) max=dp[j]+1; } } dp[i]=max; } int ret=dp[n-1]; delete[] dp; return ret;}////////////////template int Lis_Greedy(T seq[],int n){ int top=0; int *stack=new int[n]; memset(stack,0,n*sizeof(int)); stack[top]=seq[0]; for(int i=1;i<n;i++) { int low=0,high=top; while(low<=high) { int mid=(low+high)/2; if(stack[mid]<seq[i]) low=mid+1; else high=mid-1; } if(low==top+1) top++; stack[low]=seq[i]; } delete[] stack; return top+1;}int _tmain(int argc, _TCHAR* argv[]){ int testdata[10]={4,6,3,2,5,1,5,7,9,8}; cout<<"LIS by DP : "<<Lis_Dp(testdata,10)<<endl; cout<<"LIS by Greedy : "<<Lis_Greedy(testdata,10)<<endl; system("pause"); return 0;}7题目:给出一个长度为n的序列a1,a2,a3,a4,a5,a6……an,求最大连续和。换句话说,要求找到1<=i<=j<=n使得a1+a2+a3+a4+a5+a6+……+an的值尽量大。 <1>.使用枚举思想算法复杂度为O(n^2): //the method of enumerationint maxsum(int *a,int n) {int max = a[1];for (int i = 1;i <= n;i++) {int sum=0;for (int j = i;j <= n;j++) {sum +=a[j];if (sum > max)max = sum;}}return max;}<2>.使用最大连续字段和的方法,算法复杂度也为O(n^2): //the differential of the prefixint maxsum (int *a,int n) {int s[100];s[1] = a[1];int max = a[1];for (int i = 2;i <= n;i++) {s[i] = s[i-1] + a[i]; }for (i = 1;i <= n;i++) for (int j = i;j <= n;j++) {if ((s[j]-s[i]) > max) max = s[j] - s[i];}return max;}<3>.使用分治的算法,时间复杂度为O(n*log n): //Partition algorithm 划分 -> 递归 -> 合并int maxsum (int *a,int x, int y) {if (y - x == 1) return a[x];int m = x + (y-x)/2;int max = maxsum (a,x,m) > maxsum (a,m,y) ? maxsum (a,x,m) : maxsum (a,m,y);int v = 0;int L = a[m-1];for (int i = m-1;i >= x; i--) {v += a[i];if (v > L) L = v;}v = 0;int R = a[m];for (i = m;i <= y;i++) {v += a[i];if (v > R) R = v;}return (R + L) > max ? (R + L) : max;}<4>.使用动态规划算法,时间复杂度O(n): //The dynamic programmingint maxsum (int *a,int n) {int sum = 0, b = 0;for (int i = 1;i <= n;i++) {if (b > 0) b += a[i];else b = a[i];if (b > sum) sum = b;}return sum;}8.一个n*m矩阵由n行m列共n*m个数排列而成。两个矩阵A和B可以相乘当且仅当A的列数等于B的行数。一个N*M的矩阵乘以一个M*P的矩阵等于一个N*P的矩阵,运算量为nmp。矩阵乘法满足结合律,A*B*C可以表示成(A*B)*C或者是A*(B*C),两者的运算量却不同。例如当A=2*3 B=3*4 C=4*5时,(A*B)*C=64而A*(B*C)=90。显然第一种顺序节省运算量。现在给出N个矩阵,并输入N+1个数,第i个矩阵是a[i-1]*a[i]。 #include using namespace std; #define N 105 int c[N][N],s[N][N],p[N]; int chain(int i,int j) { if(c[i][j]>0)return c[i][j]; if(i==j)return 0; int u=chain(i,i)+chain(i+1,j)+p[i-1]*p[i]*p[j]; s[i][j]=i; for(int k=i+1;k<j;k++) { int t=chain(i,k)+chain(k+1,j)+p[i-1]*p[k]*p[j]; if(t>n; for(i=0;i<=n;i++) cin>>p[i]; cout<<chain(1,n)<<endl; return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值