【数论】模运算 模板及其正确性复杂度证明

\(a \cdot x = b \pmod n\)

线性同余方程


题解

法1.n,a互质时使用逆元,否则两边除掉gcd(a,x)然后逆元。(如果b除不尽及无解)
法2.化为\(a \cdot x + n \cdot k = b\) 扩展欧几里得


线性同余方程组 中国剩余定理 板题测试:51nod1079

题意

\[\begin{align} a &\equiv a_1 \pmod{p_1} \\ a &\equiv a_2 \pmod{p_2} \\ &\ldots \\ a &\equiv a_k \pmod{p_k} \end{align} \]

给出a数组和p数组(两两互质),求x


题解

可以证明模p有唯一解.\(p = p_1 p_2 \cdots p_k\)
待定系数法设\(x = x_1 + x_2 p_1 + x_3 p_1 p_2 + \ldots + x_k p_1 \ldots p_{k-1}\)
然后带入上面的n个方程。由于解的构造形式,我们可以不用高斯消元解出这个方程组复杂度依然是O(k^2)。
\(a_1 \equiv x_1 \pmod{p_1}.\)
\(a_2 \equiv x_1 + x_2 p_1 \pmod{p_2}.\)
\(\begin{array}{rclr} a_2 - x_1 &\equiv& x_2 p_1 &\pmod{p_2} \\ (a_2 - x_1) r_{12} &\equiv& x_2 &\pmod{p_2} \\ x_2 &\equiv& (a_2 - x_1) r_{12} &\pmod{p_2} \end{array}\)
其中\(r_{ij} = (p_i)^{-1} \pmod{p_j}\)
\(x_3 \equiv ((a_3 - x_1) r_{13} - x_2) r_{23} \pmod{p_3}.\)


代码

#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
//#define int long long
const int N = 0, mod = 0;
const int maxn = 15;
typedef long long ll;
int p[maxn], a[maxn], x[maxn], P[maxn];
int n;

/*模数为质数时:
ll kpow(ll x, ll y, ll mod) {
	ll ret = 1;
	while (y) {
		if (y & 1)ret = ret * x%mod;
		x = x * x%mod;
		y >>= 1;

	}
	return ret;
}
int inv(int x, int y) {
	return kpow(x, y - 2, y);
}*/
int gcd(int a, int b, int & x, int & y) {
	if (a == 0) {
		x = 0;
		y = 1;
		return b;
	}
	int x1, y1;
	int d = gcd(b % a, a, x1, y1);
	x = y1 - (b / a) * x1;
	y = x1;
	return d;
}
int inv(int x, int y) {
	int ret, tmp;
	gcd(x, y, ret, tmp);
	return ret;
}
int CRT() {
	int ret = 0;
	rep(i, 1, n) {
		x[i] = a[i];
		rep(j, 1, i - 1) {
			x[i] = inv(p[j], p[i]) * (x[i] - x[j]);

			x[i] = x[i] % p[i];
			if (x[i] < 0)
				x[i] += p[i];
		}
	}

	rep(i, 1, n) {
		ret += x[i] * P[i - 1];
	}
	return ret;
}
int main() {

	cin >> n;
	P[0] = 1;
	rep(i, 1, n) {
		cin >> p[i] >> a[i];
		P[i] = P[i - 1] * p[i];
	}
	cout << CRT() << endl;
	cin >> n;
}
/*
3
2 1
3 2
5 3
*/



\(n!{\%p}\)


题解

列出来分块\(\begin{eqnarray} n!_{\%p}&=& \underbrace{1 \cdot 2 \cdot 3 \cdot \ldots \cdot (p-2) \cdot (p-1) \cdot 1}_{1\text{st}} \cdot \underbrace{1 \cdot 2 \cdot 3 \cdot \ldots \cdot (p-2) \cdot (p-1) \cdot 2}_{2\text{nd}} \cdot \ldots \\ & & \cdot \underbrace{1 \cdot 2 \cdot 3 \cdot \ldots \cdot (p-2) \cdot (p-1) \cdot 1}_{p\text{th}} \cdot \ldots \cdot \quad \underbrace{1 \cdot 2 \cdot \cdot \ldots \cdot (n \bmod p)}_{\text{tail}} \pmod{p}. \end{eqnarray}\)
用威尔逊定理\((p-1)! \bmod p = p-1\)
每一块留下最后一个元素组成新串


代码

int factmod(int n, int p) {
    int res = 1;
    while (n > 1) {
        res = (res * ((n/p) % 2 ?  p-1 : 1)) % p;//p-1的奇数次幂为p-1,偶次幂为1
        for (int i = 2; i <= n%p; ++i)//最后一块
            res = (res * i) % p;
        n /= p;//每块最后一个元素组成新串
    }
    return res % p;
}


原根——模意义下的幂指对

题意

原根是什么啊!?
定义模n意义下的原根g (g is a primitive root modulo n):

定义:每个与n互质的数和g的幂同余(mod n)
感性上理解就是 g可以让 g^k%n 产生所有与n互质的数
从英语理解就是 g is also called the generator of the multiplicative group of integers modulo n
用数学定义g就是\(\forall a:gcd(a,n)=1 \exists k:g^k \equiv a \pmod n\)

用数学语言定义就是:设n>1,gcd(g,n)=1,称使得\(g^d \equiv 1 \pmod n\) 的最小正整数d为g对模数n的阶,记为\(\delta _ n(g)\)。 如果\(\delta _ n(g)=\phi(n)\) 则称g是n的原根。

存在性:n=1,2,4,pk,2pk.时才有,n为质数时,g^k%n取遍1道n-1.
性质1:\(g^{\phi (n)} \equiv 1 \pmod n\) 且phi(n)是最小的使其同余1的数。
性质2:\(\phi (\phi (n) )\)为n的原根数量


题解

一些定理推导,
\(g^d \not \equiv 1 \pmod n\) 本来要对所有d<phi(n)判一遍,
但是由于拉格朗日定理(orz不是群论,不是高数orz),判\(d \, | \, \phi (n)\)就够了。
为了加速枚举所有约数d,我们质因子分解phi(n),然后对每个质因子pi判一下\(d \, | \, \phi (n)\)即可
\(O(Ans . log \phi (n) . \log n)\)ans是原根的大小。


代码

int powmod (int a, int b, int p) {
    int res = 1;
    while (b)
        if (b & 1)
            res = int (res * 1ll * a % p),  --b;
        else
            a = int (a * 1ll * a % p),  b >>= 1;
    return res;
}

int generator (int p) {
    vector<int> fact;
    int phi = p-1,  n = phi;
    for (int i=2; i*i<=n; ++i)
        if (n % i == 0) {
            fact.push_back (i);
            while (n % i == 0)
                n /= i;
        }
    if (n > 1)
        fact.push_back (n);

    for (int res=2; res<=p; ++res) {
        bool ok = true;
        for (size_t i=0; i<fact.size() && ok; ++i)
            ok &= powmod (res, phi / fact[i], p) != 1;
        if (ok)  return res;
    }
    return -1;
}


\(a^x \equiv b \pmod m\)

题意

模意义下取对数,a,m互质


题解

GSBS giant step, baby step.
令x=pn-q, 则称p is giant step,q is baby step.而这个n就是控制步长比例的
原式等价于\(a^x \equiv b \pmod m,\)
\(a^{np} \equiv ba^q \pmod m\) (因为互质?)
等价于求\(f_1(p) = f_2(q).\)这两个整点函数交点\(p \in [1; \lceil \frac{m}{n} \rceil ]\) \(q \in [0; n]\)
有这么一种算法:
第一步:求出f1所有函数值并排序,
第二步:然后用所有的f2函数值再f1中用二分查找查询。
n如何选取?从复杂度来算,
第一步:\(O\left(\left\lceil \frac{m}{n} \right\rceil \left(\log m + \log \left\lceil \frac{m}{n} \right\rceil \right)\right) = O\left( \left\lceil \frac {m}{n} \right\rceil \log m\right)\)
第二步: \(O\left(n \left(\log m + \log \frac{m}{n} \right) \right) = O\left(n \log m\right).\)

\(n = \sqrt{m}.\) 时取到最小值\(O(\sqrt {m} \log m).\)


代码

//用unordered_map优化
//用迭代a来省略二次幂
//用map<int, vector<int>> 如果有多组解
int powmod(int a, int b, int m) {
    int res = 1;
    while (b > 0) {
        if (b & 1) {
            res = (res * a) % m;
        }
        a = (a * a) % m;
        b >>= 1;
    }
    return res;
}

int solve(int a, int b, int m) {
    int n = (int) sqrt (m + .0) + 1;
    map<int, int> vals;
    for (int p = n; p >= 1; --p)
        vals[powmod(a, p * n, m)] = p;
    for (int q = 0; q <= n; ++q) {
        int cur = (powmod(a, q, m) * b) % m;
        if (vals.count(cur)) {
            int ans = vals[cur] * n - q;
            return ans;
        }
    }
    return -1;
}

//用hash代替map
//非互质情况也能做
const int MAXN = (int)1e6+7;

const int mod=76543;
struct HaHashsh{
    int head[mod],p;
    struct ss{int v;int w,last;}G[77000];
    void clear(){memset(head,0,sizeof(head));p=0;}
    int find(int a){
        int A=a%mod;
        for(int i=head[A];i;i=G[i].last)
        if(G[i].v==a)return G[i].w;
        return -1;
    }
    void insert(int a,int b){
        int A=a%mod;
        G[++p]=(ss){a,b,head[A]};head[A]=p;
    }
}hs;

ll ExBSGS(ll a,ll b,ll MOD) {
    a = a%MOD; b = b%MOD;
    if (b == 1) return 0;
    int cnt = 0;
    ll t = 1;
    for (ll g = __gcd(a,MOD);g != 1;g = __gcd(a,MOD)) {
        if (b%g) return -1; //
        MOD /= g,b /= g;
        t = t*a/g%MOD;
        cnt ++;// x -> x1
        if (t == b) return cnt; //b1 = 1
    }
    hs.clear();
    int m = ceil(sqrt(1.0*MOD));
    ll e = 1;
    for (int i = 0;i < m;i ++) { //a^i i = [0,sqrt(m)) b*a^i
        hs.insert(e*b%MOD,i);
        e = e*a%MOD;
    }
    ll nw = t;
    for (int i = 1;i <= m+1;i ++) {
        nw = e*nw%MOD;
        int v = hs.find(nw);
        if (v != -1) {
            return i*m-v+cnt;
        }
    }
    return -1;
}

\(x^k \equiv a \pmod n\)

题意

模意义下开根,模数n是素数.


题解

用原根把原式转化为求对数:
因为n为素数,所以n的原根g可以生成1~n-1的所有数。
不妨设 \(x=(g^y)\)
\(\therefore\) 原式\(\Longleftrightarrow\) \((g^y)^k \equiv a \pmod n\)
\(\Longleftrightarrow\)\((g^k)^y \equiv a \pmod n\)
这样就变成了求x。
如何找到所有解?
\(x_0 = g^{y_0} \pmod n\) ,而 \(g^\phi (n) = 1\)
\(\therefore\) \(x^k \equiv g^{ y_0 \cdot k + l \cdot \phi (n)} \equiv a \pmod n \forall l \in Z\)(乘以任意个phi(n)).
\(\therefore\) \(x = g^{y_0 + \frac {l \cdot \phi (n)}{k}} \pmod n \forall l \in Z\)


代码

//头文件省略
int gcd(int a, int b) {
    return a ? gcd(b % a, a) : b;
}

int powmod(int a, int b, int p) {
    int res = 1;
    while (b > 0) {
        if (b & 1) {
            res = res * a % p;
        }
        a = a * a % p;
        b >>= 1;
    }
    return res;
}

// Finds the primitive root modulo p
int generator(int p) {
    vector<int> fact;
    int phi = p-1, n = phi;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            fact.push_back(i);
            while (n % i == 0)
                n /= i;
        }
    }
    if (n > 1)
        fact.push_back(n);

    for (int res = 2; res <= p; ++res) {
        bool ok = true;
        for (int factor : fact) {
            if (powmod(res, phi / factor, p) == 1) {
                ok = false;
                break;
            }
        }
        if (ok) return res;
    }
    return -1;
}

// This program finds all numbers x such that x^k = a (mod n)
int main() {
    int n, k, a;
    scanf("%d %d %d", &n, &k, &a);
    if (a == 0) {
        puts("1\n0");
        return 0;
    }

    int g = generator(n);

    // Baby-step giant-step discrete logarithm algorithm
    int sq = (int) sqrt (n + .0) + 1;
    vector<pair<int, int>> dec(sq);
    for (int i = 1; i <= sq; ++i)
        dec[i-1] = {powmod(g, i * sq * k % (n - 1), n), i};
    sort(dec.begin(), dec.end());
    int any_ans = -1;
    for (int i = 0; i < sq; ++i) {
        int my = powmod(g, i * k % (n - 1), n) * a % n;
        auto it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
        if (it != dec.end() && it->first == my) {
            any_ans = it->second * sq - i;
            break;
        }
    }
    if (any_ans == -1) {
        puts("0");
        return 0;
    }

    // Print all possible answers
    int delta = (n-1) / gcd(k, n-1);
    vector<int> ans;
    for (int cur = any_ans % delta; cur < n-1; cur += delta)
        ans.push_back(powmod(g, cur, n));
    sort(ans.begin(), ans.end());
    printf("%d\n", ans.size());
    for (int answer : ans)
        printf("%d ", answer);
}

posted @ 2019-03-21 15:59  SuuTTT  阅读(428)  评论(0编辑  收藏  举报