2021-04-18

ACM之五

又是动态规划的一周,这周继续做了许多动态规划的题目,见识了更多题目,但很多都是和我们讲过的例题类似,题目问法变了,但本质的东西还是那些,多的只是稍微的变形(戏法一样的东西)。但是,还是有很多问题值得我们再深入思考,毕竟这只是动态规划的入门,要想真正入门,真正学到精髓,对于我们新手来说还是有一定难度的。

子序列问题

对于现在的我们而言,求一维的最大子序列和已经不足为道了。
这个的状态方程应该是最简单的线性dp问题了。先来看一下求子序列和最大最小的相关问题,这个有一维和二维的两种,当然了,我只是说我做过的题中。

#include <iostream>
using namespace std;

int a[100001];
int main()
{
    int t,n,ans,sum;
    cin>>t;
    while(t--)
    {
        cin>>n;
        ans=-1000000;
        sum=-1000000;
        for(int i=0; i<n; i++)
        {
            cin>>a[i];
        }
        for(int i=0; i<n; i++)
        {
            if(sum<0)
                sum=a[i];
            else
                sum+=a[i];
            if(ans<sum)
                ans=sum;
        }
        cout<<ans<<endl;
    }
    return 0;
}

最大子矩阵和

下面,进阶一下,是二维的最大子序列和,可能现在应该叫最大子矩阵和了。这个问题,应该就是枚举法了吧,反正我之前就暴力穷举。但是,这个问题如果用枚举的话,它的复杂度太高了,特别容易超时。
正确解法:每次枚举子矩阵最上的行 u 和最下的行 d,再把这个子矩阵每一列的值相加,压缩成一个一维的数组,对这个数组求其最大子段和,这样就相当于把所有最上的行为 u 、最下的行为 d 的最大子矩阵和求出来了。


#include <iostream>
#include<cstdio>
#include<cstring>
int n;
int a[110][110];
int b[110];

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0; i<n; i++)
            for(int j=0; j<n; j++)
                scanf("%d",&a[i][j]);

        int Max = -1000000;
        for(int i=0; i<n; i++)
        {

            memset(b, 0, sizeof(b));
            for(int j=i; j<n; j++)
            {
                int sum=0;
                for(int k=0; k<n; k++)
                {
                    b[k] += a[j][k];
                    sum += b[k];
                    if(sum<0) sum = b[k];
                    if(sum>Max) Max = sum;
                }
            }
        }
        printf("%d\n",Max);
    }

    return 0;
}

方案数

这是一个简单的生存游戏,你控制一个机器人从一个棋盘的起始点(1,1)走到棋盘的终点(n,m)。游戏的规则描述如下:
1.机器人一开始在棋盘的起始点并有起始点所标有的能量。2.机器人只能向右或者向下走,并且每走一步消耗一单位能量。3.机器人不能在原地停留。4.当机器人选择了一条可行路径后,当他走到这条路径的终点时,他将只有终点所标记的能量。如上图,机器人一开始在(1,1)点,并拥有4单位能量,蓝色方块表示他所能到达的点,如果他在这次路径选择中选择的终点是(2,4)点,当他到达(2,4)点时将拥有1单位的能量,并开始下一次路径选择,直到到达(6,6)点。我们的问题是机器人有多少种方式从起点走到终点。这可能是一个很大的数,输出的结果对10000取模。主要是解题思路很普通,挺简单,但是中间的过程我就一直转不过弯来。思路是:初始化起始点方案数为1,每次枚举某一点能到达的位置,目标位置方案为能到达它的点方案数和。(这是我看的人家的,简洁明了)。


#include <iostream>
#include<stdio.h>
#include<string.h>

int n,m;
int dp[105][105];  

int aa (int x,int y)
{
    if(x<1 ||x>n ||y<1||y>m)
        return 1;
    return 0;
}

int main()
{
    int cas, t;
    scanf ("%d", &cas);
    while (cas--)
    {
        scanf ("%d%d", &n, &m);
        memset (dp, 0, sizeof (dp));
        dp[1][1] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
            {
                scanf ("%d", &t);
                for (int x = 0; x <= t ; x++)
                    for (int y = 0; x+y <= t ; y++) 
             {
                        if (x == 0 && y == 0) continue;  
               if(aa(x+i,y+j))
                            continue;
                        dp[i + x][j + y] = (dp[i][j] + dp[i + x][j + y]) % 10000;
                    }
            }
        printf ("%d\n", dp[n][m]);
    }
    return 0;
}


用for (int y = 0; x+y <= t ; y++) 枚举这个点能到的所有点(i+x,j+y)(x+y<=t)
其实这种方案数的问题一直是我的弱项,因为我在想的过程中就很容易跑偏,可能没那么 复杂的题目我可能会越想越复杂,然后做不出来。然后,但对于这道题来说,我看到还有一种方法,就是记忆化搜索,等有时间可以研究一下这个方法。

最长不降子序列

与这个类似的,还有最长不降子序列。这个上次说过了,简单看一下Super Jumping! Jumping! Jumping!题目代码吧。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
 
int a[1005],dp[1005];
const int inf = 999999999;
 
int main()
{
    int n,i,t,m,j,ans;
    while(~scanf("%d",&n),n)
    {
        memset(dp,0,sizeof(dp));
        for(i = 1;i<=n;i++)
        scanf("%d",&a[i]);
        for(i = 1;i<=n;i++)
        {
            ans = -inf;
            for(j = 0;j<i;j++)
            {
                if(a[i]>a[j])
                ans = max(ans,dp[j]);
            }
            dp[i] = ans+a[i];
        }
        ans = -inf;
        for(i = 0;i<=n;i++)
        {
            if(dp[i]>ans)
            ans = dp[i];
        }
        printf("%d\n",ans);
    }
 
    return 0;
}

最长公共子序列这个题目,在这周中还真做了不少。

最长公共子序列

反恐训练营
题目大意:输入恐怖分子的序列,输入狙击他们后的得分,输入枪中子弹的序列。问最大得分。这就是一道非常典型的最长公共子序列问题。不同之处是最长公共子串状态转换方程为:
dp[i][j]=dp[i-1][j-1]+1;
而现在要加上得分的话就进行很简单的变形:dp[i][j]=dp[i-1][j-1]+score;
上一次写过最长公共子序列的题目,就不再多说。




#include <stdio.h>
#include <string.h>
#include <iostream>
int dp[2005][2005],ar[2005];

int max(int x,int y)
{
    return x>y?x:y;
}

int main()
{
    int n,a,len1,len2,i,j;
    char str[2005],ans1[2005],ans2[2005];
    while(scanf("%d",&n)!=EOF)
    {
        scanf("%s",str);
        for (i=0; i<n; i++)
        {
            scanf("%d",&a);
            ar[str[i]]=a;
        }
        scanf("%s%s",ans1+1,ans2+1);
        len1=strlen(ans1);
        len2=strlen(ans2);
        for (i=0; i<len1; i++)dp[i][0]=0;
        for (j=0; j<len2; j++)dp[0][j]=0;
        for (i=1; i<len1; i++)
        {
            for (j=1; j<len2; j++)
            {
                if(ans1[i]==ans2[j])dp[i][j]=dp[i-1][j-1]+ar[ans1[i]];
                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
        printf("%d\n",dp[len1-1][len2-1]);
    }
    return 0;
}

总结

对于这一周acm的学习,最大的长进应该就是,对于动态规划求救的思路加强了练习,然后对于他的求解过程可以比较清晰的思路,在以后的做题中,会有比较大的帮助。但是感觉动态规划的这个线性问题还是有挺多难题的,还要多练习一下,对于我自己来说的话,其实在这周的动态规划的题目练习中,我真的会出现很多问题,不管是思路上的还是代码的实现上都有待提高。这周的题目,很多其实能够AC,很多还是因为去参考、去借鉴、去研究别人的代码而完成的。然后我希望可以在以后的学习中越学越精,可以不用别人的讲解,就做出题目。
当然这个过程是漫长的过程,还需要日积月累的,不断训练。
期待下周的进步!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值