• 方格取数(简单版)+小烈送菜(不知道哪来的题)-----------奇怪的dp增加了!


    一、方格取数:

    设有N*N的方格图(N<=20),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。

    某人从图的左上角的A(1,1) 点出发,可以向下行走,也可以向右走,直到到达右下角的B(n,n)点。在走过的路上(包括起点在内),他可以取走方格中的数(取走后的方格中将变为数字0)。此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。

     

    思路:emm...这咋写啊,好像是dp???可是你选了一个数后,第二次取数的时候不就不能取了,这不是有后效性了吗。。。

    所以这道题的思路很清奇:两个人同时走。

    这啥意思呢,简单来说,你的dp数组需要开四维,同时记录两个人的位置及状态,dp[i1][j1][i2][j2]。 

    既然我们一个人走两次会有后效性,那么我们可以认为是两个人同时在走,同时取数,这样就没有后效性了。

    同时,转移的时候,就会有四种转移过程,第一个人可以从左或是从上过来,第二个人亦是。

    转移方程:if(i1==i2&&j1==j2)dp[i1][j1][i2][j2]=max(dp[i1][j1][i2][j2],四种情况)+val[i1][j1];(这个意思是指两个人恰好走到一起去了,但是数只能取一次)

     else dp[i1][j1][i2][j2]=max(dp[i1][j1][i2][j2],四种情况)+val[i1][j1]+val[i2][j2];

    如下:

    if(i1==i2&&j1==j2)dp[i1][j1][i2][j2]=max(dp[i1][j1-1][i2][j2-1],max(dp[i1-1][j1][i2][j2-1],max(dp[i1-1][j1][i2-1][j2],dp[i1][j1-1][i2-1][j2])))+val[i1][j1];
    else dp[i1][j1][i2][j2]=max(dp[i1][j1-1][i2][j2-1],max(dp[i1-1][j1][i2][j2-1],max(dp[i1-1][j1][i2-1][j2],dp[i1][j1-1][i2-1][j2])))+val[i1][j1]+val[i2][j2];
                        

    啊啊啊啊太丑了太丑了

    还有优化的方法:我们注意到,i1+j1==i2+j2==time(因为同时走的,速度也一样)

    我们可以压缩成三维dp[i1][i2][time]遍历的时候跟上面大体一样,只要用time-i代替j就行了,注意time的循环范围!

    代码全貌:

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int maxn=21;
    int dp[21][21][21][21],val[21][21];
    int main(){
        int n;scanf("%d",&n);
        while(1){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            if(x==0&&y==0&&z==0)break;
            val[x][y]=z;
        }
        for(int i1=1;i1<=n;i1++){
            for(int j1=1;j1<=n;j1++){
                for(int i2=1;i2<=n;i2++){
                    for(int j2=1;j2<=n;j2++){
                        if(i1==i2&&j1==j2){
                            dp[i1][j1][i2][j2]=max(dp[i1][j1-1][i2][j2-1],max(dp[i1-1][j1][i2][j2-1],max(dp[i1-1][j1][i2-1][j2],dp[i1][j1-1][i2-1][j2])))+val[i1][j1];
                        }else{
                            dp[i1][j1][i2][j2]=max(dp[i1][j1-1][i2][j2-1],max(dp[i1-1][j1][i2][j2-1],max(dp[i1-1][j1][i2-1][j2],dp[i1][j1-1][i2-1][j2])))+val[i1][j1]+val[i2][j2];
                        }
                    }
                }
            }
        }
        printf("%d",dp[n][n][n][n]);
        return 0;
    }

     二、小烈送菜

    题目大意:一共有n位客人从1到n顺序排列,每位顾客i有满意值W[i],服务员小烈可以从1到n再从n走到1,在期间他到达i时可以选择送菜或不送,但是要求最后回到1时所有菜都送完,当送完菜给i后再送菜给j时,可以得到满意值Wi×Wj,问如何上菜总满意值最大(第一位被送菜的顾客不提供满意值)

    思路分析:

    这道题与方格取数很类似,小烈需要从1到n再从n到1走两遍,所以我们可以考虑同时维护两个小烈同时走,然后到n结束。

    性质1:W[i]*W[j]==W[j]*W[i]

    这意味着你从1到n走一遍和从n到1走一遍是等效的,你完全可以认为你从1到n中送菜的顾客1--->i--->j--->n满意度是W[1]*W[i]+W[i]*W[j]+W[j]*W[n],如果你反过来也一样:n--->j--->i--->1满意度是W[n]*W[j]+W[j]*W[i]+W[i]*W[1];显然两者等效。

    (通过这条性质,我们可以把思路中的两个小烈都改为从1到n去走)

    接下来我们需要定义dp数组:

    因为需要定义的无后效性,所以我们保证:当小列1位于i位置送菜,小列2位于j位置送菜时,所有位置小于i、j的顾客都已经被送过菜了。

    dp[i][j]表示小烈1位于i,小烈2位于j时的满意度最大值。

    转移方程:

    i可以从前一个状态k转移过来,j也是,但是这样去枚举k1,k2很麻烦(基本算不出来),所以我们考虑由i,j向后推。

    我们可以知道,因为dp[i][j]表示i,j之前的所有点都被遍历过,所以i,j的下一个状态一定是i,i+1或i+1,j或j+1,j或i,j+1。这个根据i,j的大小而定,dp[i][j]的下一个状态一定是i,j两个小烈中靠前的那个的前面一步(否则就不满足i,j前的所有顾客都被遍历过了)。我们可以得到这样的方程:

        if(i>j){
            dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i]*a[i+1]);
            dp[i][i+1]=max(dp[i][i+1],dp[i][j]+a[j]*a[i+1]);    
        }else{
            dp[i][j+1]=max(dp[i][j+1],dp[i][j]+a[j]*a[j+1]);
            dp[j+1][j]=max(dp[j+1][j],dp[i][j]+a[i]*a[j+1]);
        }

    这样写固然是没有问题啦,但是4行代码还是有点复杂,我们可以这样想:

    如果保证dp[i][j]中i,j的大小关系,那么就可以把转移方程缩至两个。

    我们不妨保证i>j。(在遍历的时候j从0到i-1即可)

    那么方程变成:

                dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i]*a[i+1]);
                dp[i][i+1]=max(dp[i][i+1],dp[i][j]+a[j]*a[i+1]);

    这样已经很优秀了,可是我们看到第两个方程中i+1>i,这并不满足我们的前提条件i>j

    这时候我们要用到性质2:

    dp[i][j]=dp[j][i]这很显然吧,两个小烈本质上是一样的,谁在i,谁在j不重要,结果也一定相同。

    所以我们可以改造一下dp[i][i+1],把它变成dp[i+1][i],这样就满足我们的i>j了。

    最后的完成方程:

                dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i]*a[i+1]);
                dp[i+1][i]=max(dp[i+1][i],dp[i][j]+a[j]*a[i+1]);

    代码全貌:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=5510;
    int dp[maxn][maxn],a[maxn];
    int main(){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=n;i++){
            for(int j=0;j<i;j++){//注意这里j从0开始遍历,因为存在一种可能:一个小烈送完了1到i的所有客人菜
                dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i]*a[i+1]);
                dp[i+1][i]=max(dp[i+1][i],dp[i][j]+a[j]*a[i+1]);
            }
        }
        int Max=0;
        for(int i=0;i<n;i++){
            Max=max(Max,dp[n][i]+a[i]*a[n]);
        }
        //我们假设第一个小烈已经到了终点,那么第二个小烈只需要从当前位置到n即可,因为i到n的所有点已经被第一个小烈走过了
        //而实际上n这个点是要“被送两次的”,因为小烈到n再回去的时候a[n]会用两次。
        printf("%d",Max);
        return 0;
    }
    
            
  • 相关阅读:
    原码、反码、补码,计算机中负数的表示
    [转]Vue 2.0——渐进式前端解决方案
    关于MySQL的tinyint(3)问题
    js对象的深拷贝及其的几种方法
    深入 js 深拷贝对象
    JS 数组克隆方法总结
    Undefined class constant 'MYSQL_ATTR_INIT_COMMAND'
    邮件措辞小计
    Forbidden You don't have permission to access / on this server PHP
    正则表达式
  • 原文地址:https://www.cnblogs.com/liu-yi-tong/p/13229055.html
Copyright © 2020-2023  润新知