【hdu2929】【高精度+动规】CQYZ_Vijos_P2102 越大越好

【问题描述】

  你的任务是用不超过n(n≤100)根火柴摆一个尽量大的,能被m(m≤3 000)整除的正整数,如图1-64所示。例如n=6和m=3,解为111。无解时输出-1。如下图所示:数字0到9,它们的火柴数量分别是:6、2、5、5、4、5、6、3、7、6。 这里写图片描述

【输入格式】

  若干行,每行包含两个正整数n和m。输入以n=0结束。

【输出格式】

  若干行,对应输入的n和m。

【输入样例】

6 3
5 6
0

【输出样例】

111
-1

【数据范围】

  对于20%的数据:1<=n<=20
  对于50%的数据:1<=n<=30
  对于100%的数据:1<=n<=100,1<=m<=3000。每个测试点最多不超过10组数据。
  

题解:

  白书上的题解是记录当前对m取余为j的位数为i所需要的最少火柴数,这里不去管它,我要讲的是更暴力也更容易想到的状态函数设计:
  f(i,j)表示用i根火柴能组成的被m取模为j的最大值
  设p[k]为组成k需要的火柴数,则有
    f(i,j)=max{ f(i-p[k],(j-k) mod m) | 0<=k<=9 && i-p[k]>=0 }
  很明显最终结果远远超过了long long 的数据范围,需要用到高精度运算
  题目本身不太难,主要是在写高精度时有一些小地方需要注意
  
  代码实现:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;   //0,1,2,3,4,5,6,7,8,9
const int mo=1000000,p[]={6,2,5,5,4,5,6,3,7,6};
//压一压位,优化下计算速度
struct bign{
    int z[35],len;
    bign(){
        memset(z,0,sizeof(z));
        len=1;
    }
    //最开始用的sprintf将整数转化成字符串再赋值,但显而易见太慢了
    void operator=(int x){
        memset(z,0,sizeof(z));
        len=0;
        if(x==0) len=1;
        else while(x!=0) z[len++]=x%mo,x/=mo;
    }
    friend bool operator<(bign a,bign b){
        if(a.len!=b.len) return a.len<b.len;
        for(int i=a.len-1;i>=0;i--) if(a.z[i]!=b.z[i]) return a.z[i]<b.z[i];
        return 0;
    }
    //因为题目只涉及到加法和乘法,所以只重定义了这两个运算符
    friend bign operator+(bign a,int b){
        bign c=a;
        int len=a.len;
        c.z[0]+=b;
        for(int i=0;i<len;i++) c.z[i+1]+=c.z[i]/mo,c.z[i]%=mo;
        while(c.z[len]>0) c.z[len+1]+=c.z[len]/mo,c.z[len]%=mo,len++;
        while(len>1&&c.z[len-1]==0) len--;
        c.len=len;
        return c;
    }
    friend bign operator*(bign a,int b){
        bign c;
        int len=a.len+15;
        for(int i=0;i<a.len;i++) c.z[i]+=a.z[i]*b;
        for(int i=0;i<len;i++) c.z[i+1]+=c.z[i]/mo,c.z[i]%=mo;
        while(c.z[len]>0) c.z[len+1]+=c.z[len]/mo,c.z[len]%=mo,len++;
        while(len>1&&c.z[len-1]==0) len--;
        c.len=len;
        return c;
    }
    void out(){
        printf("%d",z[len-1]);
        for(int i=len-2;i>=0;i--) printf("%06d",z[i]);
        printf("\n");
    }
}d[105][3005];
int n,m;
bool vis[105][3005]={0};
inline int in(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'|ch>'9'){if(ch=='-') f=-f;ch=getchar();}
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int main(){
    bign tmp1,tmp2,ans;
    while(1){
        n=in();
        if(n==0) break;
        memset(vis,0,sizeof(vis));
        m=in(),ans.z[0]=-1,ans.len=1;
        for(int k=1;k<=9;k++){
            tmp1=k;
            if(d[p[k]][k%m]<tmp1) d[p[k]][k%m]=tmp1;
            vis[p[k]][k%m]=1;
        }
        for(int i=1;i<=n;i++){
            for(int j=0;j<m;j++){
                if(!vis[i][j]) continue;
                tmp1=d[i][j]*10;
                for(int k=0;k<=9;k++){
                    if(i+p[k]>n) continue;
                    tmp2=tmp1+k;
                    if(d[i+p[k]][(j*10+k)%m]<tmp2) d[i+p[k]][(j*10+k)%m]=tmp2;
                    vis[i+p[k]][(j*10+k)%m]=1;
                }
            }
            if(vis[i][0]) if(ans<d[i][0]) ans=d[i][0];
        }
        ans.out();
        for(int i=0;i<=n;i++) for(int j=0;j<m;j++) d[i][j]=0;
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值