• 【普及组_在线赛】最小与最大


    【普及组_在线赛】最小与最大

    描述

    题目描述

    做过了乘积最大这道题,相信这道题也难不倒你。
    已知一个数串,可以在适当的位置加入乘号(设加了k个,当然也可不加,即分成k+1个部分),设这k+1个部分的乘积(如果k=0,则乘积即为原数串的值)对m 的余数(即mod m)为x;
    现求x能达到的最小值及该情况下k的最小值,以及x能达到的最大值及该情况下的k的最小值(可以存在x的最小值与最大值相同的情况)。

    输入

    第一行为数串,长度为n 满足2<=n<=1000,且数串中不存在0;
    第二行为m,满足2<=m<=50。

    输出

    四个数,分别为x的最小值 和 该情况下的k,以及x的最大值和 该情况下的k,中间用空格隔开。

    样例输入

    4421
    22

    样例输出

    0 1 21 0


    分析

    这题看着像数论,实际上是DP。
    首先要知道,做这题完全无须高精度(包括暴力)。
    借鉴一下人人皆知的读入优化:
    如果有个数为a,你要在末尾安上一个b,那么得出的值是10a+b
    然后直接取模。

    这题m很小,是AC的关键。
    fi,j,k表示做到前i位,前面几个已经分好的数的积为j(取模),后面没分好的那个数为k(取模),这时候的最小段数(即+1)。
    什么意思?
    我们枚举i时,前面有些数时分好的,但是还有一段你要继续在后面安数,这一段就是没分好的。
    初始化:f1,1,a1modm=1
    如何从fi,j,k转移?
    考虑两种情况:
    1. k后面安上第i+1个数。即转移到fi+1,j,(10k+ai+1)modm
    2. 在i和i+1之间添上,再安上第i+1个数。即转移到fi+1,jkmodm,a[i+1]modm。因为添加了个乘号,所以要+1
    求出所有fn,j,k后,枚举j和k,设s=jkmodm,s即为乘积。
    然后统计答案就行了。
    注意,因为这是段数,所以最后记得1
    这样就可以简单粗暴地AC了。
    时间复杂度O(nm2)


    代码

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    char str[1007];
    int m;
    int f[1001][51][51];
    int main()
    {
        freopen("minmax.in","r",stdin);
        freopen("minmax.out","w",stdout);
        scanf("%s",str+1);//字符串读入
        int len=strlen(str+1);
        scanf("%d",&m);
        int i,j,k;
        f[1][1][(str[1]-'0')%m]=1;//初始化
        int *q,*p;
        for (i=1;i<len;++i)
            for (j=0;j<=m;++j)
                for (k=0;k<=m;++k)
                {
                    q=&f[i][j][k];//利用指针优化寻址
                    if (*q)
                    {
                        p=&f[i+1][j][(k*10+str[i+1]-'0')%m];
                        if (*p)
                            *p=min(*p,*q);
                        else
                            *p=*q;
                        p=&f[i+1][j*k%m][(str[i+1]-'0')%m];
                        if (*p)
                            *p=min(*p,*q+1);
                        else
                            *p=*q+1;
                    }
                }
        int s,mins=2147483647,mins_k,maxs=-2147483648,maxs_k;
        for (i=0;i<=m;++i)//统计答案
            for (j=0;j<=m;++j)
            {
                q=&f[len][i][j];
                if (*q)
                {
                    s=i*j%m;
                    if (s<mins)
                    {
                        mins=s;
                        mins_k=*q;
                    }
                    else if (s==mins)
                        mins_k=min(mins_k,*q);
                    if (s>maxs)
                    {
                        maxs=s;
                        maxs_k=*q;
                    }
                    else if (s==maxs)
                        maxs_k=min(maxs_k,*q);
                }
            }
        printf("%d %d %d %d
    ",mins,mins_k-1,maxs,maxs_k-1);
    }

    注意事项

    1. 为什么我要设段数?因为>0,所以不用一开始赋特殊值。
    2. 为什么用指针?优化寻址,加快程序速度
    3. 题外话:我第一次交时RE,95分。调好久后发现是枚举i时将<len打成<=len,好尴尬!
    4. 题外话:我后来赋初值减少了些if语句,不知为何却慢了;我还用队列优化一下DP,存有用状态也更慢了。我第一次AC时f的每个节点多设了一个标记,但没必要。
    5. 我的方法跟别人不同,他们设两维状态。但是他们时间复杂度O(n2m),我的O(nm2),明显更优。
  • 相关阅读:
    Docker三剑客之 Compose
    cloudrea manager 调整datanode数据存储目录
    Docker Dockerfile 定制镜像(转)
    Docker配置mysql互为主从
    Docker构建redis cluster集群
    Kubernetes集群向指定节点上创建容器
    Docker私有仓库registry的搭建及使用
    Docker简介及基本应用
    Kubernetes的UI界面Kubernetes Dashboard的搭建
    java垃圾回收机制
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145297.html
Copyright © 2020-2023  润新知