分类
Level6

动态规划模板

数字三角形

f[1][1]=a[1][1];
for(int i=2;i<=n;i++)
    for(int j=1;j<=i;j++)  //状态转移方程如下
        f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);

最长上升子序列

for(int i=1;i<=n;i++)
    f[i]=1;  //只有a[i]一个数字
    for(int j=1;j<=i;j++){
        if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
}
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,f[i]);
printf("%d\n",ans);

最长公共子序列

for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        f[i][j]=max(f[i-1][j],f[i][j-1]);
        if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
    }
cout<<f[n][m]<<endl;

01背包

for(int i=1;i<=n;i++){  //前i个物品 
    int wi,ci;  //每个物品的体积、价值 
    cin>>wi>>ci;
    for(int j=1;j<=w;j++){
        if(wi>j){ //体积大于背包容量,放不下,只能不放 
            f[i][j]=f[i-1][j];
        }else{   //体积合适,可选择不放 与 放 
            f[i][j]=max(f[i-1][j],f[i-1][j-wi]+ci);
        }
    }
}
cout<<f[n][w];  //n个物品在容量为w的背包中的最大价值

完全背包

for(int i=1;i<=n;i++){ //从1到n个物品 
    for(int j=w[i];j<=m;j++){//从重量w[i]到重量m,能装一直装
        if(dp[j]<dp[j-w[i]]+c[i]){//如果原价值<dp[j-w[i]]+c[i]
            dp[j]=dp[j-w[i]]+c[i];//更新价值 
        }
    }        
}   
cout<<"max="<<dp[m]<<endl;

多重背包

多重背包转化为01背包问题 把第i种物品换成n[i]件01背包中的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度是O(V*Σn[i])。
解法一:朴素算法,每种物品多次01背包

#include<iostream>
#include<cmath>
using namespace std;
int n,m,f[6010]; //多重背包 
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int vi,wi,si;
        cin>>vi>>wi>>si;
        for(int k=1;k<=si;k++){ //最多si件 
            for(int j=m; j>=vi; j--){ //01背包,从后往前
                f[j]=max(f[j],f[j-vi]+wi);
            }   
        } 
    }
    cout<<f[m]<<endl;
    return 0;
}

解法二:二进制优化,转换为01背包 算法的复杂度由O(V*∑n[i])改进到O(V*∑logn[i]),特别注意“拆分物品”的思想和方法,即二进制拆分思想:
比如:整数7,只需要用1 2 4三个数任意组合,就能组合出 0~7 这8种可能。
再比如:整数10,只需要用1 2 4 3(注意最后一个数),就能组合出0~10这11种可能,这样n这个值就被二进制化了。因此如果要讨论10个一样的物品,就转化为讨论4个不同的物品了;而 n 个一样的物品,就转化为log2n个不同的物品进行讨论

#include<iostream>
using namespace std;
int n,m,v[50001],w[50001],s[50001],f[10001]={0},len=0;
int main(){  //多重背包转01背包问题 
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int vi,wi,si;
        cin>>vi>>wi>>si;
        if(vi>m) continue; //单个物品价格比m大,直接舍去 
        if(si>m/vi) si=m/vi; //剪枝,物品价格*数量比m大,留下部分 
        for(int j=1;j<=si;j=j*2){ //初筛的物品存到v[]、w[] 
            len++;
            v[len]=vi*j; //存入物品进行了二进制拆分 
            w[len]=wi*j;
            si-=j;
        }
        if(si>0){ //多余的继续存入v[]、w[]  
            len++;
            v[len]=si*vi;
            w[len]=si*wi;
        }
    }   
    for(int i=1;i<=len;i++){ //01背包问题 
        for(int j=m;j>=v[i];j--){
            if(f[j]<f[j-v[i]]+w[i]) f[j]=f[j-v[i]]+w[i];
        }   
    } 
    cout<<f[m]<<endl;
    return 0;
}

分组背包

一般分组背包问题中,每组中只能选择一件物品。这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有:
dp[k][v]=max{dp[k-1][v],dp[k-1][v-c[i]]+w[i]} 物品i属于组k
分组的背包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少背包问题的变形都可以转化为分组的背包问题。下面是滚动数组优化后的代码。

#include<iostream>
using namespace std;
int v,n,t,p;
int dp[201]={0},w[40],c[40],a[40][40]={0}; //a[r][c]表示第r组第 
int main(){
    cin>>v>>n>>t; //容量,物品数量,物品分组数 
    for(int i=1;i<=n;i++){
        cin>>w[i]>>c[i]>>p; //物品的体积,价值,分组 
        a[p][++a[p][0]]=i; //a[p][0]每组数量,1~a[p][0]该组物品的下标 
    }
    for(int i=1;i<=t;i++){  //枚举1~t组 
        for(int j=v;j>=0;j--){ //枚举背包容量从v~0 
            for(int k=1;k<=a[i][0];k++){ //枚举第i组的物品下标1~a[i][0] 
                if(j>=w[a[i][k]] && dp[j]<dp[j-w[a[i][k]]]+c[a[i][k]]){         
                //同组中相同容量下,更有价值的胜出 
                    dp[j]=dp[j-w[a[i][k]]]+c[a[i][k]];//更有价值则更新 
                }
            } 
        } 
    } 
                 
    cout<<dp[v]<<endl; //输出容量v的最大价值 
    return 0;
}  

区间DP

//以石子合并为例
for(int i=1;i<=n;i++) //求前缀和 
    aa[i]=aa[i-1]+a[i];   
memset(dp,0x3f,sizeof(dp));//初始化为极大值 
for(int i=1;i<=n;i++) dp[i][i]=0;//初始化1个石子合并 
for(int len=2;len<=n;len++){ //枚举合并的长度
    for(int i=1;i<=n-len+1;i++){ //枚举起点
        int j=i+len-1; //i为起点,j为终点
        for(int k=i;k<j;k++){ //合并i~k,k+1~j 
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+aa[j]-aa[i-1]); 
        } 
    }
}
cout<<dp[1][n]<<endl;//最小分数