【笔记】动态规划w

背包

http://blog.csdn.net/lyhvoyage/article/details/8545852
(个人认为这个不知名大佬写的不错,再就是去看看背包九讲的前几讲emmmmm)

01

有N件物品和一个容量为V的背包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大
eg:http://codevs.cn/problem/5709/

#include<bits/stdc++.h>
using namespace std;
const int sz = 100010;
const int inf = 214748364;
typedef long long LL;
#define ri register int
inline void rd(int &x){
    x=0;bool f=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    if(f) x*=-1;
}
int a[sz],v,n,ans;
int w[sz],k[sz],dp[1010][1010];
int main()
{
    rd(v),rd(n);
    for(ri i=1;i<=n;++i)
        rd(w[i]),rd(k[i]);

    for(ri i=1;i<=n;++i)
        for(ri j=1;j<=v;j++)
        if(j>=w[i])
        dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+k[i]);
        else
        dp[i][j]=dp[i-1][j];
    cout<<dp[n][v];
    return 0;
}

这是二维转移,对于第一维我们可以优化掉,优化空间

#include<bits/stdc++.h>
using namespace std;
int F[1001];
int c[1001],v[1001]; 
int main()
{
    int s,n;
    cin>>s>>n;
    for(int k=1;k<=n;k++) cin>>c[k]>>v[k];

    for(int i=1;i<=n;i++)
      for(int j=s;j>=c[i];j--)//那个物体可以放进去 
        F[j]=max(F[j],F[j-c[i]]+v[i]);

    cout<<F[s];//询问能否将一个容量s的背包填到最大价值 
    return 0;
}

完全

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
同样有二维和一维写法

/*完全背包 每一个物品都有无限个
状态转移方程,d[i][j]=max(dp[i-1][j],dp[i-1][j-k*c[i]]+k*w[i]),if(j-k*c[i]>=0)
边界 dp[0][j]=0 j>=0&&j<=v尽量填充背包,不要求装满  dp[0][0]=0 dp[0][j]=-INF j>=1&&j<=v 恰好装满*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int V=1000,N=100;
int dp[N][V],c[N],w[N];
int main()
{
    memset(dp,0,sizeof(dp));
    int v,n;
    cin>>n>>v;
    for(int i=1;i<=n;i++)
        cin>>c[i]>>w[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=v;j++)
            dp[i][j]=dp[i-1][j];
        for(int j=c[i];j<=v;j++)
            for(int k=1;k*c[i]<=j;k++)
            dp[i][j]=max(dp[i][j],dp[i-1][j-k*c[i]]+k*w[i]);
    }
    cout<<dp[n][v]<<" ";
    return 0;
}

一维写法:

#include<bits/stdc++.h>
using namespace std;
const int V=1000,N=100;
int f[N],c[N],w[N];
int main(){
    int v,n;
    cin>>v>>n;
    for(int i=1;i<=n;i++)
        cin>>c[i]>>w[i];
    for(int i=1;i<=n;++i)
        for(int j=c[i];j<=v;++j)
        f[j]=max(f[j],f[j-c[i]]+w[i]);
    cout<<f[v];
    return 0;
}

多重

多重背包问题要求简单,就是每件物品给出确定的件数,求可得到的最大价值
多重背包转换成 01 背包问题就是多了个初始化,把它的件数C 用二进制分解成若干个件数的集合,这里面数字可以组合成任意小于等于C的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可以用数字的二进制形式来解释
比如:7的二进制 7 = 111 它可以分解成 001 010 100 这三个数可以组合成任意小于等于7 的数,而且每种组合都会得到不同的数
拆分出来不全为1的数,比如13 = 1101 则分解为 0001 0010 0100 0110,前三个数字可以组合成 7以内任意一个数,即1、2、4可以组合为1——7内所有的数,加上 0110 = 6 可以组合成任意一个大于6 小于等于13的数,比如12,可以让前面贡献6且后面也贡献6就行了。虽然有重复但总是能把 13 以内所有的数都考虑到了,基于这种思想去把多件物品转换为,多种一件物品,就可用01 背包求解
二进制分解+01

#include<bits/stdc++.h>
using namespace std;
const int sz = 1010; 
int T,V,n,i,j,k,v[sz],w[sz],c[sz],dp[sz];
//v[]存价值,w[]存尺寸,c[]存件数 本题中价值是米重量,尺寸是米价格 
int main()
{
int sum,Value[sz],size[sz];
//sum存储分解完后的物品总数
//Value存储分解完后每件物品的价值
//size存储分解完后每件物品的大小 
cin>>T;
while(T--)
{
    sum=0;
    cin>>V>>n;
    for(i=0;i<n;i++)
    {
        cin>>w[i]>>v[i]>>c[i];
        //对该种类的c[i]件物品进行二进制分解
        for(j=1;j<=c[i];j<<=1)//相当于x2
        {
            Value[sum]=j*v[i];
            size[++sum]=j*w[i];
            c[i]-=j;
        }
        if(c[i]>0)
        {
            Value[sum]=c[i]*v[i];
            size[++sum]=c[i]*w[i];
        }
    }
    //经过上面对每一种物品的分解,
    //现在Value[]存的就是分解后的物品价值
    //size[]存的就是分解后的物品尺寸
    //sum就相当于原来的n   下面就直接用01背包算法来解
    memset(dp,0,sizeof(dp));
    for(i=0;i<sum;i++)
        for(j=V;j>=size[i];j--)
            if(dp[j]<dp[j-size[i]]+Value[i])
            dp[j]=dp[j-size[i]]+Value[i];
    cout<<dp[V]<<endl;
}
return 0;
}

两种包一起

#include<bits/stdc++.h>
using namespace std;
const int sz = 100010;
const int inf = 214748364;
typedef long long LL;
#define ri register int
inline void rd(int &x){
    x=0;bool f=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    if(f) x*=-1;
}
int n,m,dp[1010],t;
int p[1010],h[1010],c[1010];
void wan(int v,int w){
    for(int i=v;i<=n;i++)
        if(dp[i]<dp[i-v]+w)  
            dp[i]=dp[i-v]+w;  
}
void ling(int v,int w){
    for(ri i=n;i>=v;--i)
        if(dp[i]<dp[i-v]+w)
            dp[i]=dp[i-v]+w;
}
int main()
{
rd(t);
while(t--)
{
    memset(dp,0,sizeof(dp));
    rd(n),rd(m);
    for(ri i=1;i<=m;++i)
    {
        rd(p[i]),rd(h[i]),rd(c[i]);
        if(p[i]*c[i]>=n)
            wan(p[i],h[i]);
        else
        {
            for(ri j=1;j<c[i];j<<1)
            {
                ling(j*p[i],j*h[i]);
                c[i]=c[i]-j;
            }
        }
    }
    printf("%d\n",dp[n]);
}
    return 0;
}

混合

背包体积为V ,给出N个物品,每个物品占用体积为Vi,价值为Wi,每个物品要么至多取1件,要么至多取mi件(mi > 1) , 要么数量无限 , 在所装物品总体积不超过V的前提下所装物品的价值的和的最大值是多少?

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    int v,n;
    while(scanf("%d%d",&v,&n)!=EOF)
    {
        int w[1000],c[1000],p[1000],i,k=0,t,j,f[202]={0};
        for(i=1;i<=n;i++)
        {
            t=1;
            scanf("%d%d%d",&w[i],&c[i],&p[i]);
            if(p[i]>1) //将有限个相同价格的物品转化为不同价格的单个物品 
            {
                while(p[i]>t)
                {
                    k++;
                    w[n+k]=w[i]*t;
                    c[n+k]=c[i]*t;
                    p[n+k]=1;
                    p[i]-=t;
                    t*=2;
                }
                w[i]*=p[i];
                c[i]*=p[i];
                p[i]=1;
            }
        }
        for(i=1;i<=n+k;i++)
         if(p[i]==1)   //判断是01背包还是完全背包 
          for(j=v;j>=w[i];j--)
           f[j]=f[j]>f[j-w[i]]+c[i] ? f[j]:f[j-w[i]]+c[i];
         else
          for(j=w[i];j<=v;j++)
           f[j]=f[j]>f[j-w[i]]+c[i] ? f[j]:f[j-w[i]]+c[i];
        printf("%d\n",f[v]);
    }
    return 0;
}

划分型

目前涉及到的划分型dp不多,算上数的划分这个题目的话也是寥寥几个,不知道总结的对不对,总之先把自己的想法写出来
eg:乘积最大 http://codevs.cn/problem/1017/
划分型dp和区间型dp区分上目前还未细致研究,但是听说挺相似的2333
对于划分dp应该是有明显的划分条件,将什么什么划分到哪里or在一个连续的阶段中插入分层处理。
个人觉得有时候这个分析很不好分析,对于这种问题我们要结合连续阶段分析划分的情况怎么处理,有时候我们会选择三层for循环来枚举断点,在断点处进行选择插入断点新情况和不选择插入的老情况的比较,进行答案的统计和更新

#include<bits/stdc++.h>
using namespace std;
#define sz 110
char x[1010];
int a[sz][sz],f[sz][sz],n,m;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%c",&x[i]),a[i][i]=x[i]-'0';
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            a[i][j]=a[i][j-1]*10+x[j]-'0';//i位->j位的数字
    for(int i=1;i<=n;i++)
        f[i][0]=a[1][i];//初始化
    for(int k=1;k<=m;k++)//循环分割次数
        for(int i=k+1;i<=n;i++)//分割k次至少需要k+1位数字
            for(int j=k;j<i;j++)//循环分割位置
                f[i][k]=max(f[i][k],f[j][k-1]*a[j+1][i]);//前面*后面的数字
    printf("%d\n",f[n][m]);
    return 0;
}

树型DP

eg:
二维:没有上司的舞会
三维:愚蠢的矿工
树型DP的套路一般是处理树结构上的问题,当然区间和序列也可以转化成树来处理。树型DP出现指数级枚举组合数时,采用左儿子右兄弟法来处理

#include<iostream>
#include<cstdio>
#include<cstring>
#define RI register int
using namespace std;
const int sz = 100000;
int fir[sz],nxt[sz],dp[sz][2];    
int root,tot = 1,n,ru[sz];
bool use[sz];
struct ed{
    int t,d;
}l[sz];
inline void build(int f,int t)
{
    l[tot].t = t;
    nxt[tot] = fir[f];
    fir[f] = tot ++;
}
void dfs(int p,int f)
{
    use[p] = 1;
    for(RI i = fir[p] ; i ; i = nxt[i])
    {
        int t = l[i].t;
        if(t != f && use[t]!=1)
        {
            use[t] = 1;
            dfs(t,p);
            dp[p][0] = max(dp[p][0],dp[t][1]+dp[p][0]);
            dp[p][1] = max(dp[p][1],max(dp[t][1],dp[t][0])+dp[p][1]);
        }
    }
}
int f,t;
int main()
{
    cin>>n;
    for(RI i = 1 ; i <= n ; i ++)
        scanf("%d",&dp[i][0]);
    for(RI i = 1 ; i < n ; i ++)
    {
        scanf("%d%d",&f,&t);
        ru[f] ++;
        build(f,t);
        build(t,f);
    }
    scanf("%d%d",&root,&root);
    for(RI i = 1 ; i <= n ; i ++)
        if(ru[i] == 0)
        {
            root = i;
            break;
        }
    dfs(root,-1);
    cout<<max(dp[root][0],dp[root][1])<<'\n';
    return 0;
}

棋盘型DP

eg:传纸条
前两个和后两个分别代表了两条不同的路径的状态,通过不同的转移(主要是那一长串max)来完成答案的统计

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define RI register int
const int sz = 100010;
const int inf = 1e8;
int m,n,x,f[60][60][60][60],a[55][55];
int main()
{
    cin>>m>>n;
    for(RI i=1;i<=m;++i)
    for(RI j=1;j<=n;++j)
    {
        cin>>x;
        a[i][j]=x;
    }
for(RI i=1;i<=m;++i)
for(RI j=1;j<=n;++j)
for(RI k=1;k<=m;++k)
for(RI l=1;l<=n;++l)
{
    f[i][j][k][l]=max( f[i-1][j][k-1][l], max(f[i][j-1][k-1][l],max(f[i-1][j][k][l-1],f[i][j-1][k][l-1]) ) )+a[i][j]+a[k][l];
    if(i==k&&j==l)
        f[i][j][k][l]-=a[i][j];
}
    printf("%d",f[m][n][m][n]);
    return 0;
}

还有像是乌龟棋这样的棋盘型的核心

f[0][0][0][0]=step[1];
for(int i=0;i<=s1;i++)
for(int j=0;j<=s2;j++)
for(int k=0;k<=s3;k++)
for(int l=0;l<=s4;l++)
{
    now=i+j*2+k*3+l*4+1;
    if(i>0) f[i][j][k][l]=
        max(f[i][j][k][l],f[i-1][j][k][l]+step[now]);
    if(j>0) f[i][j][k][l]=
        max(f[i][j][k][l],f[i][j-1][k][l]+step[now]);
    if(k>0) f[i][j][k][l]=
        max(f[i][j][k][l],f[i][j][k-1][l]+step[now]);
    if(l>0) f[i][j][k][l]=
        max(f[i][j][k][l],f[i][j][k][l-1]+step[now]);
    }
printf("%d\n",f[s1][s2][s3][s4]);

其他dp?

其实感觉这个更像是递推什么的emmmm
但是不加滚动数组的这个只能处理1w左右的数据,否则会TLE

eg:矿工配餐

五维DP只要不是很丧病,一般就是第一维枚举位置状态,第二三维表示第一个情况的选择状态,第四五维表示第二个情况的选择状态,最终用总的状态求解
多用于求相同的两者的符合题意状态之和的最大或最小值

eg:以使得两个煤矿的产煤量的总和最大

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define ri register int
const int sz = 1000010;
inline void read(int &x){
    x=0;bool fl=0;char c=getchar();
    while(c<'0'||c>'9')
    {if(c=='-') fl=1;c=getchar();}
    while(c>='0'&&c<='9')
    {x=x*10+c-'0';c=getchar();}
    if(fl) x*=-1;
}
inline int check(int a,int b,int c)
{
    if(a==0&&b==0) return 1;
    if(a==0) return 1+(b!=c);//代表如果b!=c返回1 
    if(a==b&&b==c) return 1;
    if(a==b||b==c||a==c) return 2;
    return 3;
}
int t[sz],n,f[sz][4][4][4][4];
char s[sz];
int dp(int pos,int a,int b,int x,int y)
{
    if(pos==n+1) return 0;
    if(f[pos][a][b][x][y]!=-1)
        return f[pos][a][b][x][y];
    return f[pos][a][b][x][y]=max(dp(pos+1,b,t[pos],x,y)+check(a,b,t[pos]),dp(pos+1,a,b,y,t[pos])+check(x,y,t[pos]));
}
int main()
{
    read(n);
    scanf("%s",s);
    for(ri i=0;i<n;++i)//!从0开始读 
        if( s[i] == 'B' ) t[i+1] = 1;
        else if( s[i] == 'M' ) t[i+1] = 2;
        else t[i + 1] = 3;
    memset(f,-1,sizeof(f));
    cout<<dp(1,0,0,0,0);
    return 0;
}
发布了75 篇原创文章 · 获赞 80 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36693514/article/details/78337478
今日推荐