题目链接
本题是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;
}