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

来源:互联网 发布:语音模拟软件 编辑:程序博客网 时间:2024/06/03 23:43

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

描述

题目描述

做过了乘积最大这道题,相信这道题也难不倒你。
已知一个数串,可以在适当的位置加入乘号(设加了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),明显更优。
原创粉丝点击