题意
传送门 POJ 3373
题解
置换 n n n 部分数位上的数字得到 m m m,其能被 k k k 整除,且置换数位最少,有多个 m m m 满足上述条件则求最小值。
考虑到 k ≤ 1 0 4 k\leq 10^4 k≤104,则置换数位最多 4 4 4 位,此时 m m m 满足 m % k = 0 m\%k=0 m%k=0。设 m m m 数位个数为 l e n len len
m = a 0 × 1 0 l e n − 1 + a 1 × 1 0 l e n − 2 + ⋯ + a l e n − 1 × 1 0 0 m=a_{0}\times10^{len-1}+a_{1}\times10^{len-2}+\dots +a_{len-1}\times 10^0 m=a0×10len−1+a1×10len−2+⋯+alen−1×100
考虑模运算加法,则
∑ i = 0 l e n − 1 a i × 1 0 l e n − 1 − i ( m o d k ) ≡ 0 ( m o d k ) \sum\limits_{i=0}^{len-1}a_{i}\times 10^{len-1-i}(mod\ k)\equiv 0(mod\ k) i=0∑len−1ai×10len−1−i(mod k)≡0(mod k)
设 k k k 数位为 l e n ′ len' len′,则最多枚举 m i n ( l e n ′ , 4 ) min(len',4) min(len′,4) 个不同数位,置换数字,即可求得答案。
折半枚举以降低复杂度。预先求各数位上
[
0
,
9
]
[0,9]
[0,9] 模
k
k
k 的值,枚举置换
1
1
1 和
2
2
2 个数位的情况,分别排序。按置换数位为
0
0
0 到
4
4
4 个的顺序折半枚举(这部分代码基本就无限复制),此时保证置换数位最少;二分满足条件的组合,更新最小答案即可。
复杂度分析
考虑枚举数位为 4 4 4 的复杂度。因为 n ≤ 1 0 100 n\leq 10^{100} n≤10100,且十进制每位上数字只可能为 [ 0 , 9 ] [0,9] [0,9],故枚举任意 2 2 2 个不同数位的非原数位数字的组合复杂度为 O ( l e n 2 × 1 0 2 ) O(len^{2}\times 10^{2}) O(len2×102)。折半枚举的排序、二分,复杂度
O ( ( l e n 2 × 1 0 2 ) l o g ( l e n 2 × 1 0 2 ) ) O((len^{2}\times 10^{2})log(len^{2}\times 10^{2})) O((len2×102)log(len2×102))
枚举出满足条件的置换组合,按字典序更新最小答案,复杂度 O ( l e n ) O(len) O(len);考虑到数字模 k k k 均匀分布,设当前枚举数字为 d d d,则满足 ( d + d ′ ) ≡ 0 ( m o d k ) (d+d')\equiv 0(mod\ k) (d+d′)≡0(mod k) 的概率为 1 / k 1/k 1/k。则枚举位数为 4 4 4 时总的时间复杂度为
O ( ( l e n 2 × 1 0 2 ) l o g ( l e n 2 × 1 0 2 ) + l e n / k × ( l e n 2 × 1 0 2 ) l o g ( l e n 2 × 1 0 2 ) ) O((len^{2}\times 10^{2})log(len^{2}\times 10^{2})+len/k\times (len^{2}\times 10^{2})log(len^{2}\times 10^{2})) O((len2×102)log(len2×102)+len/k×(len2×102)log(len2×102))
因为枚举位数为 4 4 4,则 k k k 至少为 4 4 4 位数,故 l e n / k len/k len/k 项可忽略。估算最坏时间复杂度为 O ( 1 0 7 ) O(10^7) O(107)。
代码
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 105
#define digit 10
int k, len, n[maxn];
int rme[maxn], rmd[digit], rm[maxn][digit];
char nn[maxn + 1], tn[maxn + 1], res[maxn + 1];
// 计算 [0,9] * [10^0,10^(len-1)] (mod k)
void init()
{
len = strlen(nn);
for (int i = 0; i < len; i++)
n[i] = nn[i] - '0';
rme[0] = 1 % k;
for (int i = 1; i < len; i++)
rme[i] = rme[i - 1] * 10 % k;
for (int i = 0; i < digit; i++)
rmd[i] = i % k;
for (int i = 0; i < len; i++)
{
for (int j = 0; j < digit; j++)
{
rm[i][j] = rme[i] * rmd[j] % k;
}
}
}
#define maxp1 1005
#define maxp2 1000005
struct P1
{
int x, p, d;
P1() {}
P1(int x, int p, int d) : x(x), p(p), d(d) {}
bool operator<(const P1 &a) const
{
return x < a.x;
}
} p1[maxp1];
struct P2
{
int x, p1, d1, p2, d2;
P2() {}
P2(int x, int p1, int d1, int p2, int d2) : x(x), p1(p1), d1(d1), p2(p2), d2(d2) {}
bool operator<(const P2 &a) const
{
return x < a.x;
}
} p2[maxp2];
void solve()
{
// 求 n (mod k)
int rn = 0;
for (int i = 0; i < len; i++)
{
rn = (rn + rm[len - 1 - i][n[i]]) % k;
}
int n1 = 0, n2 = 0;
// 枚举 1 位置换位
for (int i = 0; i < len; i++)
{
int pos = len - 1 - i;
for (int d = ((i == 0 && len > 1) ? 1 : 0); d < digit; d++)
{
if (d == n[i])
continue;
p1[n1++] = P1((rm[pos][d] - rm[pos][n[i]] + k) % k, i, d);
}
}
// 枚举 2 位置换位
for (int i = 0; i < len; i++)
{
int pi = len - 1 - i;
for (int di = ((i == 0 && len > 1) ? 1 : 0); di < digit; di++)
{
if (di == n[i])
continue;
int tmp = rm[pi][di] - rm[pi][n[i]] + k;
for (int j = i + 1; j < len; j++)
{
int pj = len - 1 - j;
for (int dj = 0; dj < digit; dj++)
{
if (dj == n[j])
continue;
p2[n2++] = P2((tmp + rm[pj][dj] - rm[pj][n[j]] + k) % k, i, di, j, dj);
}
}
}
}
// 开始折半枚举
bool flag = 0;
// 置换数字个数为 0
if (rn == 0)
{
puts(nn);
return;
}
// 置换数字个数为 1
for (int i = 0; i < n1; i++)
{
if ((rn + p1[i].x) % k == 0)
{
memcpy(tn, nn, sizeof(nn));
tn[p1[i].p] = p1[i].d + '0';
if (!flag || strcmp(res, tn) > 0)
{
memcpy(res, tn, sizeof(tn));
}
flag = 1;
}
}
if (flag)
{
puts(res);
return;
}
// 置换数字个数为 2
sort(p1, p1 + n1);
for (int i = 0; i < n1; i++)
{
int tmp = k - (rn + p1[i].x) % k;
int p = lower_bound(p1, p1 + n1, P1(tmp, 0, 0)) - p1;
while (p < n1 && p1[p].x == tmp)
{
if ((p1[p].p == p1[i].p))
{
++p;
continue;
}
P1 &pp = p1[p++];
memcpy(tn, nn, sizeof(nn));
tn[p1[i].p] = p1[i].d + '0', tn[pp.p] = pp.d + '0';
if (!flag || strcmp(res, tn) > 0)
{
memcpy(res, tn, sizeof(tn));
}
flag = 1;
}
}
if (flag)
{
puts(res);
return;
}
// 置换数字个数为 3
sort(p2, p2 + n2);
for (int i = 0; i < n1; i++)
{
int tmp = k - (rn + p1[i].x) % k;
int p = lower_bound(p2, p2 + n2, P2(tmp, 0, 0, 0, 0)) - p2;
while (p < n2 && p2[p].x == tmp)
{
if (p2[p].p1 == p1[i].p || p2[p].p2 == p1[i].p)
{
++p;
continue;
}
P2 &pp = p2[p++];
memcpy(tn, nn, sizeof(nn));
tn[p1[i].p] = p1[i].d + '0', tn[pp.p1] = pp.d1 + '0', tn[pp.p2] = pp.d2 + '0';
if (!flag || strcmp(res, tn) > 0)
{
memcpy(res, tn, sizeof(tn));
}
flag = 1;
}
}
if (flag)
{
puts(res);
return;
}
// 置换数字个数为 4
for (int i = 0; i < n2; i++)
{
int tmp = k - (rn + p2[i].x) % k;
int p = lower_bound(p2, p2 + n2, P2(tmp, 0, 0, 0, 0)) - p2;
while (p < n2 && p2[p].x == tmp)
{
if (p2[p].p1 == p2[i].p1 || p2[p].p2 == p2[i].p1 || p2[p].p1 == p2[i].p2 || p2[p].p2 == p2[i].p2)
{
++p;
continue;
}
P2 &pp = p2[p++];
memcpy(tn, nn, sizeof(nn));
tn[p2[i].p1] = p2[i].d1 + '0', tn[p2[i].p2] = p2[i].d2 + '0';
tn[pp.p1] = pp.d1 + '0', tn[pp.p2] = pp.d2 + '0';
if (!flag || strcmp(res, tn) > 0)
{
memcpy(res, tn, sizeof(tn));
}
flag = 1;
}
}
puts(res);
}
int main()
{
while (~scanf("%s %d", nn, &k))
{
init();
solve();
}
return 0;
}