POJ 3373 模运算 + 折半枚举

题意

传送门 POJ 3373

题解

置换 n n n 部分数位上的数字得到 m m m,其能被 k k k 整除,且置换数位最少,有多个 m m m 满足上述条件则求最小值。

考虑到 k ≤ 1 0 4 k\leq 10^4 k104,则置换数位最多 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×10len1+a1×10len2++alen1×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=0len1ai×10len1i(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} n10100,且十进制每位上数字只可能为 [ 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值