chap3.动态规划
动态规划的基本要素
重要性质:
最优子结构:包含子结构的最优解。自底向上,首先假设最优解导出的子问题解不是最优,再设法说明在这个假设下可构造出比原问题最优解更好的解,从而矛盾
重叠子问题:有的子问题被不断反复计算。
变形:
备忘录方法:自顶向下(和动态规划的区别),对每个子问题进行存储,部分子问题不用求解的时候,使用备忘录法有利
步骤:
找出最优解的性质,并刻画其结构特征
递归地定义最优值
以自底向上的方式计算最优值
根据计算最优值得到的信息构造最优解
矩阵连乘O(n3)
从小到大计算,每次将当前的直接与之前的相乘,然后开始拆分最优子结构进行组合找更优的组合。
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int n; int m; int f[1000][1000]; int s[1000][1000]; int p[1000]; void solve() { for(int i=1;i<=n;i++) { f[i][i]=0; } for(int r=2;r<=n;r++) { for(int i=1;i<=n-r+1;i++) { int j=i+r-1; f[i][j]=f[i+1][j]+p[i-1]*p[i]*p[j];//先计算当前的 s[i][j]=i; for(int k=i+1;k<j;k++)//找最优子结构 { int t=f[i][k]+f[k+1][j]+p[i-1]*p[k]*p[j]; if(t<f[i][j]) { f[i][j]=t; s[i][j]=k; } } } } } void traceback(int i,int j) { if(i==j)return; traceback(i,s[i][j]); traceback(s[i][j]+1,j); cout<<"multi A "<<i<<", "<<s[i][j]; cout<<" and A "<<s[i][j]+1<<", "<<endl; } int main() { srand(time(0)); cin >> n>>m; p[1]=rand()%10; for(int i=2;i<=n;i+=2) { p[i]=p[i+1]=rand()%10; } for(int i=1;i<=n;i+=1) { cout<<p[i]<<" "; } cout<<endl; solve(); cout<<f[1][n]; traceback(1,n); }
最长公共子序列O(nm)
一共三种情况,
ai==bi,那么长度加一。
c[i-1][j]>=c[i][j-1]:a的前i-1个和b的前j个公共子序列长度大于a的前i个和b的前j-1个公共子序列长度,所以Xi和Yj的公共子序列和Xi-1 Yj的一样
c[i-1][j]<c[i][j-1]:a的前i-1个和b的前j个公共子序列长度小于a的前i个和b的前j-1个公共子序列长度,所以Xi和Yj的公共子序列和Xi Yj-1的一样
按照这三种情况标记,即可进行递归找出序列对应的数字
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int n; int m; int a[1000];//n int b[1000];//m int c[1000][1000]; int x[1000][1000]; void solve() { for(int i=1;i<=n;i++) { c[i][0]=0; } for(int j=1;j<=m;j++) { c[0][j]=0; } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(a[i]==b[j]) { c[i][j]=c[i-1][j-1]+1; x[i][j]=1; } else if(c[i-1][j]>=c[i][j-1]) { c[i][j]=c[i-1][j]; x[i][j]=2; } else { c[i][j]=c[i][j-1]; x[i][j]=3; } } } } void out(int i,int j) { if(i==0||j==0)return ; if(x[i][j]==1) { out(i-1,j-1);cout<<a[i]<<" "; } else if(x[i][j]==2) { out(i-1,j); } else out(i,j-1); } int main() { srand(time(0)); cin >> n>>m; for(int i=1;i<=n;i++) { a[i]=rand()%10; cout<<a[i]<<" "; } cout<<endl; for(int i=1;i<=m;i++) { b[i]=rand()%10; cout<<b[i]<<" "; } cout<<endl; solve(); cout<<"maxn: "<<c[n][m]<<endl; out(n,m); }
最大子段和O(n)
每次比较当前值,若大于累计和+当前值,则只用当前值,否则继续加。
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int n; int s[10000]; int sum; void solve() { int now=0; for(int i=1;i<=n;i++) { if(now+s[i]<s[i]) { now=s[i]; } else now=now+s[i]; sum=max(sum,now); } } int main() { srand(time(0)); cin >> n; for(int i=1;i<=n;i++) { int zhengfu=rand()%31; s[i]=rand()%10; if(zhengfu&1)s[i]=-s[i]; cout<<s[i]<<" "; } cout<<endl; solve(); cout<<sum<<endl; }
延伸:最大子矩阵和O(n3)
和最大子段和差不多,只不过是把矩阵压成一行,相当于遍历每种压缩的可能性进行最大子段和操作
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int n; int m; int p[10000][10000]; int s[10000]; int maxnum() { int sum=0; int now=0; for(int i=1;i<=n;i++) { if(now+s[i]<s[i]) { now=s[i]; } else now=now+s[i]; sum=max(sum,now); } return sum; } void solve() { int sum=0; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { s[j]=0; } for(int j=i;j<=n;j++) { for(int k=1;k<=m;k++) { s[k]+=p[j][k]; } sum=max(sum,maxnum()); } } cout<<sum<<endl; } int main() { srand(time(0)); cin >> n>>m; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { int zhengfu=rand()%31; p[i][j]=rand()%10; if(zhengfu&1)p[i][j]=-p[i][j]; cout<<p[i][j]<<" "; }cout<<endl; } cout<<endl; solve(); }
最大m子段和问题
动态规划,借助矩阵可以直观的看到计算过程。
定义二维数组dp, dp[ i ][ j ],表示前 j 项所构成 i 子段的最大和,且必须包含着第j项,即以第j项结尾然后是一个递推过程。
求dp[ i ][ j ],有两种情况
1、dp[ i ][ j ] = dp[ i ] [ j-1 ] + a[ j ] ,即把第j项融合到第 j-1 项的子段中,子段数没变
2、dp[ i ][ j ] = dp[ i-1 ] [ t ] + a[ j ],(i-1<= t < j )
把第 j 项作为单独的一个子段,然后找一下i-1个子段时,最大的和,然后加上a[ j ] 然后比较上面两种情况,取大的。
下面看图,红色数字为输入的序列:

如图,要求dp[ 3 ][ 6 ],只需比较他左边的那个,和上面那一行圈起来的之中最大的数, 再加上a[ j ] 即为dp[ 3 ][ 6 ] 的值。
优化一下:
1、沿着第m行的最后一个元素,往左上方向画一条线,线右上方的元素是没必要计算的那么dp[ i ][ j ] ,j++的时候,j的上限为 i + n - m 即可。还有左下角那一半矩阵,也是不用计算的,因为1个数字不可能分为2个子段
2、每确定一个dp[ i ][ j ],只需用到本行和上一行,所以不用开维数组也可以,省内存。开两个一维数组,pre和dp,pre记录上一行,dp记录当前行
3、再对上一行红圈中的数字找最大值时,若用一个循环来找,容易超时。
优化方法:在每次计算dp之前,同时记录下j前面的最大元素。时间复杂度大致为O(m*(n-m+1)),mn-m方通过图片,分析情况1和2,就能发现,从左上角走到第 m 行的最后一个元素即可,找出第 m 行的最大值即为答案。
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int n; int m; int s[10000]; int c[10000]; int b[10000]; void solve() { b[0]=0; c[1]=0; for(int i=1;i<=m;i++) { b[i]=b[i-1]+s[i]; c[i-1]=b[i]; int maxn=b[i]; for(int j=i+1;j<=i+n-m;j++) { b[j]=b[j-1]>c[j-1]?b[j-1]+s[j]:c[j-1]+s[j];//b[j-1]:直接连 c[j-1]新开一段 c[j-1]=maxn;//之前的最大选法方案 maxn=max(maxn,b[j]); } c[i+n-m]=maxn; } int sum=0; for(int i=m;i<=n;i++) { if(sum<b[i]) { sum=b[i]; } } cout<<sum<<endl; } int main() { srand(time(0)); cin >> n>>m; for(int i=1;i<=n;i++) { int zhengfu=rand()%31; s[i]=rand()%10+1; if(zhengfu&1)s[i]=-s[i]; cout<<s[i]<<" "; } cout<<endl; solve(); }
流水作业调度
n个作业{1,2,…,n},要在由机器M1和M2组成的流水线上完成加工。
每个作业加工的顺序都是先在M1上加工,然后在M2上加工。
M1和M2加工作业i所需的时间分别为ai和bi。
要求确定这n个作业的最优加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间最少。

令N1={i ai<bi},N2={i ai>bi}
将N1中依ai递增排序,N2中按bi递减排序
然后N1+N2即为解
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); struct node { int key; int index; bool job; bool operator<(const node&a)const { return key<=a.key; } }; int n; int a[1000]; int b[1000]; int c[1000]; node p[1000]; int c1,c2; int head=1; int tail; void solve() { for(int i=1;i<=n;i++) { p[i].key=a[i]>b[i]?b[i]:a[i]; p[i].job=a[i]<=b[i]; p[i].index=i; } sort(p+1,p+1+n); tail=n; for(int i=1;i<=n;i++) { if(p[i].job) { c[head++]=p[i].index; } else c[tail--]=p[i].index; } c1=a[c[1]]; c2=c1+b[c[1]]; for(int i=2;i<=n;i++) { c1+=a[c[i]]; c2=(c1>c2?c1:c2)+b[c[i]]; } } int main() { srand(time(0)); cin >> n; for(int i=1;i<=n;i++) { a[i]=rand()%30; b[i]=rand()%30; cout<<a[i]<<" "<<b[i]<<endl; } solve(); for(int i=1;i<=n;i++) { cout<<c[i]<<" "; } cout<<endl; cout<<"ans: "<<c2<<endl; }
图像压缩
数据结构解释:
s[]:该段的位数
l[]:该段有的元素
b[]:该段每个元素存储位数
一段s[]:

如何推导?
对于每个s[i],从当前往前进行更新,每次更新分为两部分,前一半部分是s[k],后半部分是区间bmax,相加比较出最小值就是s[i]

#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int s[1000]; int l[1000]; int b[1000]; int p[1000]; int n; int len(int x) { int le=1; x>>=1; while(x) { x>>=1; le++; } return le; } void solve() { s[0]=0; int head=11; int lmax=256; for(int i=1;i<=n;i++) { b[i]=len(p[i]);//b为一个像素的位数 int bmax=b[i]; s[i]=s[i-1]+bmax;///初始化 l[i]=1; for(int j=2;j<=i&&j<=lmax;j++)//根据前面来找最大值 { if(b[i-j+1]>bmax)//找1~i-1中的b最大值 { bmax=b[i-j+1]; } if(s[i]>s[i-j]+j*bmax)//j为个数,bmax为长度,更新s[i] { s[i]=s[i-j]+j*bmax; l[i]=j;//更新l个数 } } s[i]+=head; } } void print() { for(int i=1;i<=n;i++) { cout<<i<<" s: "<<s[i]<<" l: "<<l[i]<<" b: "<<b[i]<<endl; } } void out() { int pos=n; while(pos) { cout<<l[pos]<<" "<<b[pos]<<endl; pos=pos-l[pos]; } cout<<endl; } int main() { //ios::sync_with_stdio(false); //cin.tie(nullptr); while(1) { cin>>n; srand(time(0)); for(int i=1;i<=n;i++) { p[i]=(rand())%256; } for(int i=1;i<=n;i++) { cout<<p[i]<<" "; } cout<<endl; solve(); print(); out(); } }
0-1背包
这个是逆序写法,(书上是这样写的),但是感觉nm正序的写法更简单易懂一点
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int n; int c; int w[1000]; int v[1000]; int f[1000][1000]; int x[1000]; void solve() { int jmax=min(w[n]-1,c); for(int i=0;i<=jmax;i++) { f[n][i]=0; } for(int i=w[n];i<=c;i++)//初始化 { f[n][i]=v[n]; } for(int i=n-1;i>1;i--) { jmax=min(w[i]-1,c); for(int j=0;j<=jmax;j++)//继承 { f[i][j]=f[i+1][j]; } for(int j=w[i];j<=c;j++)//更新 { f[i][j]=max(f[i+1][j],f[i+1][j-w[i]]+v[i]); } } f[1][c]=f[2][c]; if(c>=w[1]) f[1][c]=max(f[1][c],f[2][c-w[1]]+v[1]); cout<<f[1][c]<<endl;
//输出结果 for(int i=1;i<n;i++) { if(f[i][c]==f[i+1][c]) { x[i]=0; } else { x[i]=1; c-=w[i]; } } x[n]=(f[n][c])?1:0; } int main() { srand(time(0)); cin >> n; for(int i=1;i<=n;i++) { w[i]=rand()%30; v[i]=rand()%30; c+=w[i]; cout<<w[i]<<" "<<v[i]<<endl; } c-=c/2-rand()%20; cout<<"c: "<<c<<endl; solve(); //cout<<f[1][c]<<endl; for(int i=1;i<=n;i++) { if(x[i]) cout<<i<<" "; } }

浙公网安备 33010602011771号