标签: 数论

中国剩余定理及其扩展原理及应用

中国剩余定理及其扩展原理及应用

中国剩余定理(Chinese remainder theorem)

内容

对于以下一元线性同余方程组:
(S): \begin{cases} x \equiv a_1 \pmod{m_1} \\ x \equiv a_2 \pmod{m_2} \\ \vdots \\ x \equiv a_n \pmod{m_n} \end{cases}
其中整数m_1, m_2, \ldots, m_n两两互质,则对于任意的整数a_1, a_2, \ldots, a_n,方程组 (S) 有解,并且可以用如下方式构造解:

  1. M=\prod_{i=1}^n m_iM_i = \frac{M}{m_i}
  2. M_i^{-1}M_im_i意义下的逆元,即M_iM_i^{-1} \equiv 1 \pmod{m_i}
  3. 该方程组的通解为x = kM + \sum_{i=1}^n a_iM_iM_i^{-1} \ (k \in \mathbb{Z})。在模M意义下,该方程组的唯一解为x = \sum_{i=1}^n a_iM_iM_i^{-1}

例题:中国剩余定理 问题 – 51Nod

按照上述方式构造解即可。代码如下。

// Code by KSkun, 2018/4
#include <cstdio>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 20;

int n, p[MAXN], m[MAXN];

inline LL fpow(LL n, LL k, LL p) {
    LL res = 1;
    while(k) {
        if(k & 1) res = res * n % p;
        n = n * n % p;
        k >>= 1;
    }
    return res;
}

inline LL crt() {
    LL pall = 1, res = 0;
    for(int i = 1; i <= n; i++) {
        pall *= p[i];
    }
    for(int i = 1; i <= n; i++) {
        res = (res + m[i] * pall / p[i] % pall 
            * fpow(pall / p[i], p[i] - 2, p[i]) % pall) % pall;
    }
    return res;
}

int main() {
    n = readint();
    for(int i = 1; i <= n; i++) {
        p[i] = readint(); m[i] = readint();
    }
    printf("%lld", crt());
    return 0;
}

扩展中国剩余定理(exCRT)

内容

对于CRT的扩展适用于CRT模数非互质的情况。
我们来研究两个同余方程的情况:
\begin{cases} x \equiv b_1 \pmod{a_1} \\ x \equiv b_2 \pmod{a_2} \end{cases} \Rightarrow \begin{cases} x = a_1x_1 + b_1 \\ x = a_2x_2 + b_2 \end{cases}
我们可以联立后面的展开式,得到这个式子a_1x_1 + a_2x_2 = b_2 - b_1。这个式子有解的条件是\mathrm{gcd}(a_1, a_2) | (b_2 - b_1),如果有解,我们可以利用扩展欧几里得算法(欧几里得算法和扩展欧几里得算法 | KSkun’s Blog)求出x_1的一个解(实际上求出的是方程a_1x_1 + a_2x_2 = \mathrm{gcd}(a_1, a_2)的解,需要转换,即乘一个\frac{b_2-b_1}{\mathrm{gcd}(a_1, a_2)}),回代可以得到x的一个解x'
这时,我们可以用这个方程x \equiv x' \pmod{\mathrm{lcm}(a_1, a_2)}代替原来的两个方程。
我们可以每次合并两个方程,最终得到一个同余方程,就可以求解了。

例题:2891 — Strange Way to Express Integers

直接利用上述方法解决即可。代码如下。

// Code by KSkun, 2018/4
#include <cstdio>

typedef long long LL;

const int MAXN = 100005;

LL k, a[MAXN], r[MAXN];

inline LL exgcd(LL a, LL b, LL &x, LL &y) {
    if(!b) {
        x = 1; y = 0; return a;
    }
    LL res = exgcd(b, a % b, x, y);
    LL t = x; x = y; y = t - a / b * y;
    return res;
}

inline LL excrt() {
    LL A = a[1], R = r[1], x, y;
    for(int i = 2; i <= k; i++) {
        LL g = exgcd(A, a[i], x, y);
        if((r[i] - R) % g) return -1;
        x = (r[i] - R) / g * x; x = (x % (a[i] / g) + a[i] / g) % (a[i] / g);
        R = A * x + R;
        A = A / g * a[i]; R %= A;
    }
    return R;
}

int main() {
    while(scanf("%lld", &k) != EOF) {
        for(int i = 1; i <= k; i++) {
            scanf("%lld%lld", &a[i], &r[i]);
        }
        printf("%lld\n", excrt());
    }
    return 0;
}

参考资料

[SDOI2015]序列统计 题解

[SDOI2015]序列统计 题解

题目地址:洛谷:【P3321】[SDOI2015]序列统计 – 洛谷、BZOJ:Problem 3992. — [SDOI2015]序列统计

题目描述

小C有一个集合S,里面的元素都是小于M的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S。小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个。小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案mod 1004535809的值就可以了。

输入输出格式

输入格式:
一行,四个整数,N、M、x、|S|,其中|S|为集合S中元素个数。第二行,|S|个整数,表示集合S中的所有元素。

输出格式:
一行,一个整数,表示你求出的种类数mod 1004535809的值。

输入输出样例

输入样例#1:

4 3 1 2
1 2

输出样例#1:

8

题解

参考资料:bzoj 3992: [SDOI2015]序列统计 (NTT+快速幂+DP) – CSDN博客
本题需要的数学姿势:数学笔记:数论(欧拉函数、阶、原根) | KSkun’s Blog
首先,计数自然是一个动态规划,我们考虑设计状态dp[i][j]为选了i个数且结果mod M的值是j,转移如下
dp[i][j] = \sum_{k \in S} dp[i-1][j'] \ (j'k \bmod M = j)
直观上感觉直接做是一个O(n|S|)的,好像只能得10分。
接下来我们要考虑一个问题,模数很特殊,并不是常见的1000000007,而是1004535809,它的原根为3,是常用的NTT模数,难道出题人在指引我们使用NTT?
想用NTT做,首先得搞出卷积,卷积下标是加减的事情没办法像上面那样乘吧。那么考虑利用原根把乘法转换为加法,dp[i][j]表示选到第i个数结果mod M的值为g^j,改变以后的转移方程就成这样了
dp[i][j] = \sum_{g^k \bmod M \in S} dp[i-1][j'] \ ((j' + k) \bmod (M - 1) = j)
或者这样?用f[i]表示g^i是否在集合中。
dp[i][j] = \sum_{k=1}^{M-1} dp[i-1][j-k] \cdot f[k]
咦,后面那部分现在可以卷积了,而且你会发现每一层的转移中f数组都是不变的,也就是说这是个线性变换,参照矩阵快速幂的操作,我们可以做多项式的快速幂,即dp[n] = dp[0] \times f^n。答案便是 dp[n][i] \ (g^i=x)
于是我们的思路就很明确了:求原根、算出f数组、多项式快速幂。
没完呢!这里有一个坑点,集合内的元素可以有0,0显然对答案没贡献,所以你要跳过它处理。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cmath>
#include <cstring>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline LL fpow(LL n, LL k, LL p) {
    n %= p;
    LL res = 1;
    while(k) {
        if(k & 1) res = res * n % p;
        n = n * n % p;
        k >>= 1;
    }
    return res;
}


const int MAXN = 1 << 15, G = 3, MO = 1004535809;

int n, m, x, s, t;
LL f[MAXN], g[MAXN];
bool vis[MAXN];

LL rev[MAXN], dft1[MAXN], dft2[MAXN], dftr[MAXN];

inline void calrev(int n, int len) {
    for(int i = 0; i < n; i++) {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
    }
}

inline void dft(int n, LL *arr, int f) {
    for(int i = 0; i < n; i++) {
        if(i < rev[i]) std::swap(arr[i], arr[rev[i]]);
    }
    for(int i = 1; i < n; i <<= 1) {
        LL gn = fpow(G, (MO - 1) / (i << 1), MO);
        if(f == -1) gn = fpow(gn, MO - 2, MO);
        for(int j = 0; j < n; j += i << 1) {
            LL g = 1;
            for(int k = 0; k < i; k++) {
                LL x = arr[j + k], y = g * arr[j + k + i] % MO;
                arr[j + k] = (x + y) % MO;
                arr[j + k + i] = ((x - y) % MO + MO) % MO;
                g = g * gn % MO;
            }
        }
    }
    if(f == -1) {
        LL invn = fpow(n, MO - 2, MO);
        for(int i = 0; i < n; i++) {
            arr[i] = arr[i] * invn % MO;
        }
    }
}

inline void ntt(LL *a, LL *b) {
    memset(dft1, 0, sizeof(dft1));
    memset(dft2, 0, sizeof(dft2));
    memset(dftr, 0, sizeof(dftr));
    int n, len = 0;
    for(n = 1; n <= (m - 1) << 1; n <<= 1) len++;
    for(int i = 0; i < n; i++) {
        dft1[i] = a[i];
        dft2[i] = b[i];
    }
    calrev(n, len);
    dft(n, dft1, 1); dft(n, dft2, 1);
    for(int i = 0; i < n; i++) {
        dftr[i] = dft1[i] * dft2[i] % MO;
    }
    dft(n, dftr, -1);
    for(int i = 0; i < m - 1; i++) {
        a[i] = (dftr[i] + dftr[i + m - 1]) % MO;
    }
}

inline LL calg(LL n) {
    int fact[8005], tot = 0, x = n - 1;
    for(int i = 2; i * i <= x; i++) {
        if(x % i == 0) {
            fact[tot++] = (n - 1) / i;
            while(x % i == 0) x /= i;
        }
    }
    if(x > 1) fact[tot++] = (n - 1) / x;
    bool success;
    for(int i = 2;; i++) {
        success = true;
        for(int j = 0; j < tot; j++) {
            if(fpow(i, fact[j], n) == 1) {
                success = false;
                break;
            }
        }
        if(success) return i;
    }
}

int main() {
    n = readint(); m = readint(); x = readint(); s = readint();
    for(int i = 1; i <= s; i++) {
        t = readint();
        vis[t] = 1;
    }
    int xpow, G = calg(m), gn = 1;
    for(int i = 0; i < m - 1; i++) {
        if(vis[gn]) f[i] = 1;
        if(gn == x) xpow = i;
        gn = gn * G % m;
    }
    g[0] = 1;
    for(int i = n; i; i >>= 1) {
        if(i & 1) ntt(g, f);
        ntt(f, f);
    }
    printf("%lld", g[xpow]);
    return 0;
}
[CF364D]Ghd 题解

[CF364D]Ghd 题解

题目地址:Codeforces:Problem – 364D – Codeforces、洛谷:【CF364D】Ghd – 洛谷

题目描述

John Doe offered his sister Jane Doe find the gcd of some set of numbers a.
Gcd is a positive integer g, such that all number from the set are evenly divisible by g and there isn’t such g’ (g’ > g), that all numbers of the set are evenly divisible by g’.
Unfortunately Jane couldn’t cope with the task and John offered her to find the ghd of the same subset of numbers.
Ghd is a positive integer g, such that at least half of numbers from the set are evenly divisible by g and there isn’t such g’ (g’ > g) that at least half of the numbers from the set are evenly divisible by g’.
Jane coped with the task for two hours. Please try it, too.
有一个数列,求最大的数,使得该数是数列中一半以上的数的因数。

输入输出格式

输入格式:
The first line contains an integer n (1 ≤ n ≤ 10^6) showing how many numbers are in set a. The second line contains space-separated integers a1, a2, …, an (1 ≤ ai ≤ 10^12). Please note, that given set can contain equal numbers.
Please, do not write the %lld specifier to read or write 64-bit integers in С++. It is preferred to use the %I64d specifier.

输出格式:
Print a single integer g — the Ghd of set a.

输入输出样例

输入样例#1:

6
6 2 3 4 5 6

输出样例#1:

3

输入样例#2:

5
5 5 6 10 15

输出样例#2:

5

题解

参考资料:CFR 364 D. Ghd ( Random, Math ) – 0w1
随机化的题目,直接求显然不好办,n的数据范围看着就吓人,直观感觉像O(n \log n)
我们知道这个数字一定是这个数列中某个数的因数,因此我们需要选择一个数求它与其他数的最大公因数。这些公因数中的一个就是答案。求一遍公因数是O(n \log n)的。
对于求出来的公因数,我们去从大到小找一个会成为超过一半数的因数的数字。具体做法是,选择一个因数,去找比它大的因数,如果它能整除大因数,说明大因数对应的数字也可以被这个数整除,应当把加到这个数的计数上。这个过程直观看上去是O(n^2)的,但是实际上我们不会对比当前最优解还小的因数计算,并且当计数超过n的一半的时候就可以停止计数并计入答案,在这样的处理下我们可以让这个计数的时间变得可接受。
答案肯定是这些数字中超过一半数的因数,因此每一次随机找到正确解的概率超过0.5。我们考虑重复找解的过程多次,这里我使用10次(超过10次似乎会TLE 18)。

代码

// Code by KSkun, 2018/3
#include <cstdio>
#include <ctime>
#include <cstdlib>

#include <map>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 1000005;

inline LL gcd(LL a, LL b) {
    LL t;
    while(b) {
        t = a % b;
        a = b;
        b = t;
    }
    return a;
}

int n;
LL a[MAXN];

int main() {
    srand(time(NULL));
    n = readint();
    for(int i = 1; i <= n; i++) {
        a[i] = readint();
    }
    LL ans = 1;
    for(int rep = 1; rep <= 10; rep++) { // bad rand
        int rnd = (rand() * RAND_MAX + rand()) % n + 1;
        std::map<LL, int> fact;
        for(int i = 1; i <= n; i++) {
            LL t = gcd(a[i], a[rnd]);
            if(!fact.count(t)) fact[t] = 1;
            else fact[t]++;
        }
        std::map<LL, int>::iterator it = fact.end();
        do {
            it--;
            if((*it).first <= ans) continue;
            int cnt = 0;
            for(std::map<LL, int>::iterator it1 = it; 
                it1 != fact.end() && cnt << 1 < n; it1++) {
                if(!((*it1).first % (*it).first)) {
                    cnt += (*it1).second;
                }
            }
            if(cnt << 1 >= n) ans = (*it).first;
        } while(it != fact.begin());
    }
    printf("%I64d", ans);
    return 0;
}