UVa1341/LA3262 Different Digits

题目链接

        本题是2004年ICPC 亚洲区域赛 上海赛区的题目

题意

        输入正整数n(n<65536),求它的最小倍数m,使得m 包含的不同的数字种类尽量少(例如,1334 有3 种数字:1, 3, 4)。如果有多解,m 的值应尽量小。例如,n=101时答案为1111。

分析

        非常好的一道数论综合题,涉及数论分析、剩余系、欧拉降幂、枚举、动态规划等知识点。

        首先,如果n恰好包含一种数字,答案就是n。

      如果n为质数,可以发现答案恰好包含一种数字(n=101时答案为1111,n=13时答案为111111)。

        n为合数时,要考虑其因子情况(含2、含5、其他),因子仅包含一个2不包含5(可以包含其他质数)或者仅包含一个5不包含2时,答案仍然只包含一种数字(比如n=26时答案为222222,n=35时答案为555555),其他时候的答案最多也只包含两种数字。

        什么时候答案包含两种数字呢?其实也很明确:n能被10或16或25整除。

        总结一下:n能被10或16或25整除答案恰好包含两种数字,否则为答案仅包含一种数字。

        题目还要考虑多解时m的值尽量小,答案仅仅包含一种数字时的求解可以这样做:依次枚举位数x(再枚举数字d),先求出m=11...1(连续x个1)时对n的余数r(借助欧拉降幂快速求出),如果r==0这就是答案了,不为零时++d,检查d*r%n是否为零更新答案。

        答案仅包含两种数字时,首先如果n恰好包含2种数字,答案就是n;否则,仍然借鉴前一种枚举方案求解:答案一定可以表示成dd...d(连续x个1)+ e??...?(?代表e或者0,e??...?的总位数不超过x并且d+e <= 9)。

        求出了dd...d对n的余数r,那么e??...?对n的余数一定为n-r。

        怎么找出余数为特定值时e??...?的最小值呢?用dp解决:设least[e][r]表示e??...?对n的余数为r时,最高位的e所在的最小位置,则根据余数关系即可做状态转移。

        实际求解中为了优化效率还需要按e??...?的位数小于x和等于x两种情况处理:先计算e??...?的位数小于x时答案的最高位值d,再枚举e??...?的位数等于x时的dd...d的实际位d1(d1+e <= d)。

        更多细节,详见AC代码。

AC代码


#include <iostream>
#include <cstring>
#include <string>
using namespace std;

#define N 66000
int r[N<<1], least[10][N], n;

int digits(int x) {
    bool f[10] = {0}; int cnt = 0;
    while (x > 0) f[x%10] = true, x /= 10;
    for (int i=0; i<10; ++i) if (f[i]) ++cnt;
    return cnt;
}

int phi() {
    int x = n, ans = n;
    for (int i=2; i*i <= x; ++i) if (x%i == 0) {
        ans = ans/i*(i-1);
        while (x%i == 0) x /= i;
    }
    if (x > 1) ans = ans/x*(x-1);
    return ans;
}

int pow(int x, int phi) {
    return r[x <= phi ? x : x%phi + phi];
}

int find(int d, int r) {
    int x = 0;
    for (int i=1; i<d; ++i) if (least[i][r]) {
        if (!x || least[i][r] < least[x][r]) x = i; 
    }
    return x;
}

bool cmp(const string& ans, int phi, int x, int y, int r) {
    if (ans.size() == 0) return true;
    for (int s = least[y][r%n], i=s-1; i>0 && r; i = least[y][r = (r - pow(s-1, phi)*y%n + n) % n] - 1)
        if (x+'0' < ans[s-i]) return true;
    return false;
}

void sol() {
    memset(least, 0, sizeof(least));
    int d = 0, p = phi(), sr = 1; string ans;
    r[0] = 1; for (int i=1, q=p<<1; i<q; ++i) r[i] = 10*r[i-1]%n;
    for (int i=1; !d; sr = (sr + pow(i++, p)) % n) {
        for (int j=1; !d && j<10; ++j) {
            int v = n - sr*j % n, x = find(10-j, v);
            if (x) {
                char ch = '0' + (d = j);
                for (int k=0; k<i; ++k) ans += ch;
                for (int y; y = least[x][v]; v = (v - pow(y-1, p)*x%n + n) % n) ans[i-y] += x;
            }
        }
        for (int j=1, v; j<10; ++j) {
            for (int k=0; k<n; ++k) least[0][k] = least[j][k];
            if (!least[j][v = pow(i-1, p)*j%n]) least[j][v] = i;
            for (int k=1, x; k<n; ++k) if (least[0][k] && !least[0][x=(k+v)%n]) least[j][x] = i;
        }
        for (int j=1, x = d ? d+1 : 10; j<x; ++j) for (int k=0, v, y; k<j; ++k)
        if (least[y = j-k][(v = n-sr*k%n) % n] == i && cmp(ans, p, k, y, v)) {
            char ch = '0' + k;
            if (d) for (int q=0; q<i; ++q) ans[q] = ch;
            else for (int q=0; q<i; ++q) ans += ch;
            for (int z=i; v; z = least[y][v = (v - pow(z-1, p)*y%n + n) % n]) ans[i-z] += y;
            x = d = j;
            break;
        }
    }
    cout << ans;
}

void solve() {
    int c = digits(n);
    if (c > 1) {
        if (n%10==0 || n%16==0 || n%25==0) {
            if (c > 2) sol();
            else cout << n;
        } else {
            int d = 0, r = 1, sr = 1;
            for (int i=2; !d; ++i) {
                r = 10*r%n; sr = (sr + r) % n;
                for (int j=1; j<10; ++j) if (sr*j%n == 0) {
                    d = j;
                    for (int k=0; k<i; ++k) cout << d;
                    break;
                }
            }
        }
    } else cout << n;
    cout << endl;
}

int main() {
    while (cin>>n && n) solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值