POJ2635同余模定理与千进制

POJ2635同余模定理与千进制

基础知识点:同余模定理

所谓的同余,顾名思义,就是两个(或多个)数对同一个数做取模运算所得结果相同。如 a = 11 与 b = 6 对 n = 5 同余,用符号表示就是 a mod n = b mod n = 1 或者a%n = b%n = 1

同余模定理包含很多子定理,常用的有以下两个:

(a+b)%n = (a%n+b%n)%n = (a%n+b)%n = (a+b%n)%n;

(a*b)%n = (a%n*b%n)%n;

先看十进制下情况(十进制指最小单元a/b/c/d均为小于十的数字):

a%n = A

(ab)%n = (b+10*a)%n = (b%n+10*a%n)%n = (b%n+10*A)%n = B
//ab表示一个两位数,以此类推

(abc)%n = (c+10*(ab))%n = (c%n+10*(ab)%n)%n = (c%n+10*B)%n = C

(abcd)%n = (d+10*(abc)%n)%n = (d%n+10*(abc)%n)%n = (d%n+10*C)%n

另外由于a/b/c/d都较小,为减少求模次数,由(a%n+b)%n = (a+b)%n

(ab)%n = (b+10*A)%n = B

(abc)%n = (c+10*B)%n = C

(abcd)%n = (d+10*C)%n

由此我们可以得到对一个无法用整数类型储存的大数从最高位开始逐位取模计算的方法:

#include <cstdio>
const int maxn = 1005;
char num[maxn];
int main() {
    int n;
    while (~scanf("%s%d", num, &n)) {
    //与scanf("%s%d", num, &n) != EOF作用相同
        int ans = 0;
        for (int i = 0; num[i]; i++)
            ans = ((ans * 10 + (num[i] - '0'))% n;
        printf("%d\n", ans);
    }
    return 0;
}

防止TLE要点一:改用千进制,减少求模次数

如果你曾经在OJ上试验过,你会发现把a[1000]改为a[10]并不能减少耗时,反之也不会增加耗时。此处原理类似,对两位数取模与对四位数取模每次运算耗时几乎相同,而减少求模次数后整体耗时将大大减少(千进制下求模次数是十进制的三分之一)。

防止TLE要点二:改进素数生成方法

博主最开始采用的是最朴素的素数打表方法:


int cnt;
int plist[maxn];
bool isPrime(int n) {
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0)
            return false;
    return true;
}
void getPrime(int n) {
    for (int i = 2; i < n; i++)
        if (isPrime(i))
            plist[cnt++] = i;
}

时间复杂度O(n3/2)
结果当然是TLE
为此我们需要改用时间复杂度O(n)的欧拉生成法.

AC代码(844ms):

#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1000010;
char a[101];
int ka[35], prime[maxn];
int n, cnt;

int getPrime(int n) {
    for (int i = 2; i <= n; i++) {
        if (!prime[i])
            prime[++*prime] = i;//*prime用来计数,prime[1]=2
        for (int j = 1; j <= *prime && i * prime[j] <= n; j++) {
            prime[prime[j] * i] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
    return *prime;
}
int cal(char c, int n = 1) { return (c - '0') * n; }
int main() {
    getPrime(maxn);//要生成比1000000大的素数
    while (scanf("%s%d", a, &n) && n) {
        memset(ka, 0, sizeof(ka));
        cnt = 0;
        for (int i = strlen(a) - 1; i >= 2; i -= 3) {
            ka[cnt++] = cal(a[i - 2], 100) + cal(a[i - 1], 10) + cal(a[i]);
        }
        switch (strlen(a) % 3) {
        case 1:
            ka[cnt++] = cal(a[0]);
            break;
        case 2:
            ka[cnt++] = cal(a[0], 10) + cal(a[1]);
            break;
        }

        int ans, res;
        bool bad = false;
        for (int j = 1; prime[j] < n && !bad; j++) { //(*)
            int k = prime[j];
            ans = 0;
            for (int i = cnt - 1; i >= 0; i--)
                ans = (ans * 1000 + ka[i]) % k;
            if (ans == 0) {
                bad = true;
                res = k;
            }
        }
        if (!bad)
            printf("GOOD\n");
        else
            printf("BAD %d\n", res);
    }
    return 0;
}

其中需要注意的一点是素数表里要有比1000000大的素数,否则n=1000000时(*)处循环不能及时终止会造成数组越界访问。

更慢的的万进制

最后,博主突发奇想,如果采用万进制,会不会更快呢?
AC代码(1297ms):

#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1000010;
char a[101];
int wa[26]; //wa这个名字只是巧合
int n, cnt;
int prime[maxn];
int getPrime(int n) {
    for (int i = 2; i <= n; i++) {
        if (!prime[i])
            prime[++*prime] = i;
        for (int j = 1; j <= *prime && i * prime[j] <= n; j++) {
            prime[prime[j] * i] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
    return *prime;
}
int cal(char c, int n = 1) { return (c - '0') * n; }
int main() {
    getPrime(maxn);
    while (scanf("%s%d", a, &n) && n) {
        memset(wa, 0, sizeof(wa));
        cnt = 0;
        for (int i = strlen(a) - 1; i >= 3; i -= 4) {
            wa[cnt++] = cal(a[i - 3], 1000) + cal(a[i - 2], 100) +
                        cal(a[i - 1], 10) + cal(a[i]);
        }
        switch (strlen(a) % 4) {
        case 1:
            wa[cnt++] = cal(a[0]);
            break;
        case 2:
            wa[cnt++] = cal(a[0], 10) + cal(a[1]);
            break;
        case 3:
            wa[cnt++] = cal(a[0], 100) + cal(a[1], 10) + cal(a[2]);
            break;
        }

        long long ans; //int会溢出
        int res;
        bool bad = false;
        for (int j = 1; prime[j] < n && !bad; j++) {
            int k = prime[j];
            ans = 0;
            for (int i = cnt - 1; i >= 0; i--)
                ans = (ans * 10000 + wa[i]) % k;
            if (ans == 0) {
                bad = true;
                res = k;
            }
        }
        if (!bad)
            printf("GOOD\n");
        else
            printf("BAD %d\n", res);
    }
    return 0;
}

结果却令人失望,为什么更慢了呢?其实这也不难理解,对五位数求模的单次耗时相比对四位数显著增加。这样看,千进制大概是一个较好的平衡点。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值