【笔记】动态规划w

来源:互联网 发布:ubuntu trusty 源 编辑:程序博客网 时间:2024/05/17 03:59

  • 背包
    • 01
    • 完全
    • 多重
    • 混合
  • 划分型
  • 树型DP
  • 棋盘型DP
  • 其他dp

背包

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 intinline 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 intinline 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 110char 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 intusing 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 intconst 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 intconst 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;}
原创粉丝点击