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

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

描述

题目描述

做过了乘积最大这道题,相信这道题也难不倒你。
已知一个数串,可以在适当的位置加入乘号(设加了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\n",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),明显更优。
posted @ 2017-12-10 19:21  jz_597  阅读(139)  评论(0编辑  收藏  举报