zoukankan      html  css  js  c++  java
  • 洛谷专题-更要技巧的动规与记忆化

    P1064金明的预算方案(有依赖的背包问题)

    题意:

    金明有n元钱,每个物件有对应的价值与重要程度,金明希望购物物品的价值乘上重要程度和最大,请求出最大和,现在有些物品为一些其他物品的附属品,如果要买归类为附件的物品,必须先买该附件所属的主件

    思路:

    带有附件的背包问题,它属于01背包的变式。

    即然物品分为主件和附件两类,且每个主件最多包含两个附件,那么我们不妨枚举所有的主件。那么,对于每次枚举,会有五种情况:

    • 什么都不买
    • 只买主件
    • 买主件和第一个附件
    • 买主件和第二个附件
    • 买主件和两个附件

    只要把这五种情况最终的价值算出来,取最大值就可以了

    #include <iostream>
    #define maxn 32005
    using namespace std;
    int n,m;
    int v,p,q;
    int main_item_w[maxn];
    int main_item_c[maxn];
    int annex_item_w[maxn][3];
    int annex_item_c[maxn][3];
    int f[maxn];
    int main(){
        cin >> n >> m;
        for (int i=1;i<=m;i++){
            cin >> v >> p >> q;
            if (!q){
                main_item_w[i] = v;
                main_item_c[i] = v * p;
            }
            else{
                annex_item_w[q][0]++;
                annex_item_w[q][annex_item_w[q][0]] = v;
                annex_item_c[q][annex_item_w[q][0]] = v * p;
            }
        }
    
        for (int i=1;i<=m;i++)
            for (int j=n;main_item_w[i]!=0 && j>=main_item_w[i];j--){
                f[j] = max(f[j],f[j-main_item_w[i]]+main_item_c[i]);
    
                if (j >= main_item_w[i] + annex_item_w[i][1])
                    f[j] = max(f[j],f[ j - main_item_w[i] - annex_item_w[i][1] ] + main_item_c[i] + annex_item_c[i][1]);
    
                if (j >= main_item_w[i] + annex_item_w[i][2])
                    f[j] = max(f[j],f[ j - main_item_w[i] - annex_item_w[i][2] ] + main_item_c[i] + annex_item_c[i][2]);
    
                if (j >= main_item_w[i] + annex_item_w[i][1] + annex_item_w[i][2])
                    f[j] = max(f[j],f[ j - main_item_w[i] - annex_item_w[i][1] - annex_item_w[i][2] ] + main_item_c[i] + annex_item_c[i][1] + annex_item_c[i][2]);
    
             }
         cout << f[n] << endl;
         return 0;
    }
    View Code

     P1541 乌龟棋

    题意:

    有四种牌,代表分别可向前跳1,2,3,4步,并且有一定的数量限制,得分为路径点的权值和,问从起点跳到终点的得分最大值是多少

    思路:

    一共有四张卡片,代表所能走的不同的步数,也就是当前数的取得,有可能是在前一个数的基础上,走了一步/两步/三步/四步而来,因而考虑用一个四维的状态数组f[i][j][k][h]分别表示卡片的张数,DP列出所有可能的状态即可。

    要注意的细节:

    第一格是唯一的起点,所以f[0][0][0][0]=a[1],也就是一张卡片都没有用的时候,取得的初值。

    如果用了第一张卡片,则用f[1][0][0][0]表示,有f[1][0][0][0]=f[0][0][0][0]+a[2],也就是从第一格开始,走了一格,实际上要取的数是a[2],也就是a[t+1],其中t为卡片所规定的步数。

    每张卡片只能用一次,但每一种卡片不只一张,因此在输入的时候,需要做个统计。

    注意动规的初值,循环要从0开始,表示没有用到相应的卡片。

    写法一:DP

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
     using namespace std;
     const int maxn=41;
     int dp[maxn][maxn][maxn][maxn],a[1000],b[5];
     int main()
     {
         int n,m,x;
         scanf("%d%d%",&n,&m);
         for(int i=1;i<=n;i++) scanf("%d",&a[i]);
         for(int i=1;i<=m;i++){
             scanf("%d",&x);
             b[x]++;
         }
        dp[0][0][0][0]=a[1];
        for(int i=0;i<=b[1];i++){
            for(int j=0;j<=b[2];j++){
                for(int k=0;k<=b[3];k++){
                    for(int l=0;l<=b[4];l++){
                        int t=a[i+2*j+3*k+4*l+1];
                        if(i!=0) dp[i][j][k][l]=max(dp[i-1][j][k][l]+t,dp[i][j][k][l]);
                        if(j!=0) dp[i][j][k][l]=max(dp[i][j-1][k][l]+t,dp[i][j][k][l]);
                        if(k!=0) dp[i][j][k][l]=max(dp[i][j][k-1][l]+t,dp[i][j][k][l]);
                        if(l!=0) dp[i][j][k][l]=max(dp[i][j][k][l-1]+t,dp[i][j][k][l]);
                    }
                }
            }
        }
        cout<<dp[b[1]][b[2]][b[3]][b[4]]<<endl;
     }
    View Code

    写法二:记忆化搜索

    include<bits/stdc++.h>
    using namespace std;
    int f[41][41][41][41];
    int n;
    int s[351];
    int tot[5];
    inline int dfs(int a,int b,int c,int d)
    {
        if(f[a][b][c][d]!=0) return f[a][b][c][d];
        if(a)f[a][b][c][d]=max(f[a][b][c][d],dfs(a-1,b,c,d));
        if(b)f[a][b][c][d]=max(f[a][b][c][d],dfs(a,b-1,c,d));
        if(c)f[a][b][c][d]=max(f[a][b][c][d],dfs(a,b,c-1,d));
        if(d)f[a][b][c][d]=max(f[a][b][c][d],dfs(a,b,c,d-1));
        f[a][b][c][d]+=s[a+2*b+3*c+4*d+1];
        return f[a][b][c][d];
    }
    int main()
    {
        int n,m;
        cin>>n>>m;
        for(register int i=1;i<=n;i++)
        cin>>s[i];
        int q;
        for(register int i=1;i<=m;i++)
        {
            cin>>q;
            tot[q]++;
        }
        f[0][0][0][0]=s[1];
        cout<<dfs(tot[1],tot[2],tot[3],tot[4])<<endl;
        return 0;
    }
    View Code

     P1026统计单次个数(Hash+DP)

    题意:

    给你一段字符串,已经一些单次,现在让你把字符串分成k段,使得每段的单次个数和最大

    思路:

    定义DP[i][j]表示前i个字母分成j段得到的最大单词数,答案是DP[len][k],可以初始化一下F[i][i]和F[i][1]. 方程F(i,j)=max{ F(r,j-1)+w(r+1,i) (r=j...i-1) }. 意思就是把1..r的字母先分成j-1段,剩下的r+1..i的字母分成另一段。

    先预处理出w[i][j],表示从i到j的单词数。可以倒着推,w[i][j]=w[i+1][j];(如果存在从A[i]字母开始的单词,则w[i][j]=w[i+1][j]+1.出现同一字母开头的多个单词也还是加1就够了.)

    至于怎么判断一段里有多少个单次,我就选择了用哈希来处理

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
     using namespace std;
     const int ran=233;
     const int mod=1e9+7;
     int n,k,s;
     char ch[300],f[10][300];
     int ha[300],sum[300][300],dp[300][300];
     int hash(char *c)
     {
         int len=strlen(c);
         int ans=0;
         for(int i=0;i<len;i++)
             ans=(ans*ran+c[i])%mod;
         return ans;
     }
     int judge(int x,int y)
     {
         char t[300];
         for(int i=1;i<=s;i++){
             memset(t,0,sizeof(t));
             int len=strlen(f[i]);
             strncpy(t,ch+x,min(len,y-x+1));
             if(hash(t)==ha[i])return 1;
         }
        return 0;
     }
     int main()
     {
         scanf("%d%d",&n,&k);
         for(int i=1;i<=n;i++)
             scanf("%s",ch+(i-1)*20);
         scanf("%d",&s);
         for(int i=1;i<=s;i++) scanf("%s",f[i]);
         for(int i=1;i<=s;i++) ha[i]=hash(f[i]);    
         int len=strlen(ch);
         for(int i=len-1;i>=0;i--){
             for(int j=i;j>=0;j--){
                 sum[j][i]=sum[j+1][i];
                 if(judge(j,i)) sum[j][i]++;
             }
         }
        
        for(int i=0;i<len;i++){
            for(int j=1;j<=k;j++){
                if(j==1) dp[i][j]=sum[0][i];
                else{
                    for(int l=0;l<i;l++){
                        if(dp[l][j-1])
                            dp[i][j]=max(dp[i][j],dp[l][j-1]+sum[l+1][i]);
                    }
                }
            }
        }
        cout<<dp[len-1][k]<<endl;
        return 0;
     }
    View Code

     P1063能量项链(区间DP)

    思路:

    重点就是将整体划分为区间,小区间之间合并获得大区间

    状态转移方程的推导如下

    一、将珠子划分为两个珠子一个区间时,这个区间的能量=左边珠子*右边珠子*右边珠子的下一个珠子

    二、区间包含3个珠子,可以是左边单个珠子的区间+右边两珠子的区间,或者左边两珠子的区间右边+单个珠子的区间 。即,先合并两个珠子的区间,释放能量,加上单个珠子区间的能量(单个珠子没有能量。。) ,Energy=max(两个珠子的区间的能量+单个珠子区间的能量,单个珠子的区间的能量+两个珠子的区间的能量 )

    三、继续推4个珠子的区间,5个珠子的区间。 于是可以得到方程:Energy=max(不操作的能量,左区间合并后的能量+右区间合并后的能量+两区间合并产生能量)

    两区间合并后产生的能量=左区间第一个珠子*右区间第一个珠子*总区间后面的一个珠子

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
     using namespace std;
     const int maxn=205;
     struct node{
         int x,y;
     }a[maxn];
     int dp[maxn][maxn];
     int main()
     {
         int n;
         scanf("%d",& n);
         for(int i=1;i<=n;i++){
             scanf("%d",&a[i].x);
             a[i-1].y=a[i].x;
         }
        a[n].y=a[1].x;
        for(int i=1;i<n;i++) a[i+n]=a[i];
        memset(dp,0,sizeof(dp));
        for(int len=1;len<=n;len++){
            for(int i=1;i<=2*n-len+1;i++){
                for(int k=i;k<i+len;k++){
                    int j=i+len;
                    int t=a[i].x*a[k].y*a[j].y;
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+t);    
                }
            }
        }
        int ans=0;
        for(int i=1;i<=n;i++)    ans=max(ans,dp[i][i+n-1]);
        cout<<ans<<endl;
        return 0;
      } 
    View Code

     P1156 垃圾陷阱(01背包,记忆化搜索)

    题意:

    有一只牛在井底,一些时间会从天上丢下垃圾,牛可以选择吃多,多活f秒,或者堆起来增加h高度,问牛最快什么时候能出来,出不来的话最多能活多久

    思路:

    ①记忆化搜索:没什么好说的,搜就完事了

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<map>
    #define inf 0x3f3f3f3f
    const int maxn=105;
    using namespace std;
    struct node{
        int t,f,h;
    }a[105];
    int D,G,ok,max_time=0,min_time=inf;
    map<int,map<int,map<int,bool> > > ed;
    void dfs(int cnt,int hi,int tim)
    {
        if(ed[cnt][hi][tim]) return;
        ed[cnt][hi][tim]=1;
        if(cnt>G+1) return;
        if(tim-(a[cnt].t-a[cnt-1].t)<0){
            max_time=max(max_time,a[cnt-1].t+tim);
            return;
        }
        else tim-=(a[cnt].t-a[cnt-1].t);
        if(hi+a[cnt].h>=D){
            ok=1;
            min_time=min(min_time,a[cnt].t);
        }
        dfs(cnt+1,hi+a[cnt].h,tim);
        dfs(cnt+1,hi,tim+a[cnt].f);
        return;
    }
    int cmp(node a,node b){return a.t<b.t;}
      int main()
      {
          scanf("%d%d",&D,&G);
          for(int i=1;i<=G;i++) scanf("%d%d%d",&a[i].t,&a[i].f,&a[i].h);
          sort(a+1,a+1+G,cmp);
          a[G+1].t=inf;
          dfs(1,0,10);
          if(ok) cout<<min_time<<endl;
          else cout<<max_time<<endl;
        return 0;  
      }
    View Code

    ②01背包:这个题非常的像0101背包,但是又有不同之处,因为0101背包中的“不装”就不对状态做修改,这里不装则是将垃圾堆起来

    当不选用这个垃圾来当垫子时:f[i+1][j]=max(f[i+1][j],f[i][j]+num[i].f)

    当选用这个垃圾来当垫子时:f[i+1][j+h[i]]=max(f[i+1][j+num[i].h],f[i][j])

    num[].h表示垃圾可以垫高的高度,num[].f表示吃垃圾可以维持的生命多少

    对于爬不出的情况:ans=max(ans,f[i][0]) (1<=i<=n)

    初始化:f[0][0]=10

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define inf 0x3f3f3f3f 
     using namespace std;
     const int maxn=105;
     struct node{
         int t,f,h;
     }a[105];
     int dp[maxn][maxn];
     int cmp(node a,node b){return a.t<b.t;}
     int main()
     {
         int h,n;
         scanf("%d%d",&h,&n);
         for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].t,&a[i].f,&a[i].h);
         sort(a+1,a+1+n,cmp);
         dp[0][0]=10;
         for(int i=0;i<n;i++){
             for(int j=0;j<=h;j++){
                 if(dp[i][j]>=a[i+1].t){
                    int hight=j+a[i+1].h;
                     if(hight>=h){
                         cout<<a[i+1].t<<endl;
                         return 0;
                     }
                     dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i+1].f);
                     dp[i+1][hight]=max(dp[i+1][hight],dp[i][j]);
                 }            
             }
         }
        int ans=-inf;
        for(int i=1;i<=n;i++) ans=max(ans,dp[i][0]);
        cout<<ans<<endl;
        return 0;
     }
    View Code

     P1052过河(路径压缩)

    题意:

    青蛙每次可以跳[S,T]步,在一些位置上有石头,问青蛙从起点跳到终点,最少要跳到多少个石头上

    思路:

    仔细分析题目,会发现,石头的最大值只有100个,也就是说,在一个很长的数轴上,只有很少的点,这是一个很稀疏的图, 根据离散的思维,可以把两点之间的距离大幅地缩减。

    路径压缩分析

    压缩的范围可以从题目中找方法,题目说青蛙每次只能跳1<=s<=t<=10的距离,所以对1-10求最小公倍数,答案是2520,也就是说,如果从a点出发,一定能跳到 a+2520 点和 a+x*2520的点

    设当前是从 a 点跳到 b 点, b 点可能有石头(说明 a 点和 b 点之间,一定没有石头,怎么跳都不影响答案);

    按照上面的分析:可以分情况讨论:

    1 如果 a < b< a+2520,则正常操作; 

    2.如果 a + 2520 < b,则让 a 先跳到 a+2520 的点上,再重复 1 的操作;

    3 依次类推,如果 a + x * 2520 < b,可以看作 a 可以直接飞过中间的x个区间,到离 b 最近的一个位置,在按照1的操作执行;

    根据以上的操作,可以把任意两个点之间的距离d,压缩到 2520 以内,因此 f 数组的空间就可以从原来 10^9^ 缩小到 50W以内,然后就可以正常跑DP了。

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #define inf 0x3f3f3f3f
     using namespace std;
     const int maxn=1e6+10;
     const int mod=2520;
     int a[maxn],b[maxn],dp[maxn],flag[maxn];
     int main()
     {
         int l,s,t,m,n;
         cin>>l>>s>>t>>m;
         memset(flag,0,sizeof(flag));
         for(int i=1;i<=m;i++) scanf("%d",&a[i]);
         sort(a+1,a+1+m);
         for(int i=1;i<=m;i++) b[i]=(a[i]-a[i-1])%mod;
         for(int i=1;i<=m;i++){
             a[i]=a[i-1]+b[i];
             flag[a[i]]=1;
         }
        n=a[m];
        memset(dp,inf,sizeof(dp));
        dp[0]=0;
        for(int i=0;i<=n+t;i++){
            for(int j=s;j<=t;j++)
                if(i-j>=0)
                    dp[i]=min(dp[i],dp[i-j]+flag[i]);
        }
        int ans=inf;
        for(int i=n;i<=n+t;i++) ans=min(dp[i],ans);
        cout<<ans<<endl;
        return 0;
     }
    View Code
  • 相关阅读:
    MQTT
    群晖搭建webssh
    OSI 协议
    centos7 yum安装ffmpeg,以及ffmpeg的简单用法
    centos7 RTMP直播服务器搭建
    elasticsearch
    H5的storage
    bootstrap 列表組
    eclipse的debug模式下启动不了tomcat
    bootstrap collapse
  • 原文地址:https://www.cnblogs.com/overrate-wsj/p/12345284.html
Copyright © 2011-2022 走看看