小言_互联网的博客

解题报告(十三)中国剩余定理(ACM / OI)

467人阅读  评论(0)

整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


繁凡出品的全新系列:解题报告系列 —— 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数( 1 ∼ 5 1 \sim 5 15),以模板题难度 1 1 1 为基准。


这样大家在学习算法的时候就可以执行这样的流程:

%
阅读【学习笔记】 / 【算法全家桶】学习算法 ⇒ \Rightarrow 阅读相应算法的【解题报告】获得高质量题单 ⇒ \Rightarrow 根据一句话题解的提示尝试自己解决问题 ⇒ \Rightarrow 点开详细题解链接学习巩固(好耶)
%
要是26个英文字母用完了我就接上24个希腊字母,我就不信50道题不够我刷的hhh
%
解题报告系列合集:【解题报告系列】超高质量题单 + 题解(ICPC / CCPC / NOIP / NOI / CF / AT / NC / P / BZOJ)

本题单前置知识:《算法竞赛中的初等数论》(二)正文 0x20同余(ACM / OI / MO)(十五万字符数论书)

A、(P3868 [TJOI2009])猜数字

Weblink

https://www.luogu.com.cn/problem/P3868

Problem

Solution

b i   ∣   ( n − a + i ) n − a i % b i = 0 n − a i ≡ 0 ( m o d    b i ) n ≡ a i ( m o d    b i )

b i   |   ( n a + i ) n a i % b i = 0 n a i 0 ( mod b i ) n a i ( mod b i )
bi  (na+i)nai%bi=0nai0(modbi)nai(modbi)

显然有 n n n 个同余方程,直接CRT解方程组即可。

注意数据保证 ∏ i = 0 k b i ≤ 1 0 18 \prod\limits_{i=0}^{k}b_i\le 10^{18} i=0kbi1018,即 M ≤ 1 0 18 M\le 10^{18} M1018,那么CRT的过程中随便一乘就会爆 long long ,所以要用快速乘。用 l o g n logn logn 的快速乘竟然 T 了…所以需要加一些经典优化或者用 O ( 1 ) O(1) O(1) 的快速乘,可以处理小于 1.7 × 1 0 308 1.7\times 10^{308} 1.7×10308 的数据

Code

#include <bits/stdc++.h>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#include <queue>
using namespace std;
#define int long long
typedef long long ll;
typedef int itn;
typedef pair<int, int> PII;
typedef pair<double, int> PDI;
const int N = 50 + 7, mod = 1e18 + 7, INF = 0x3f3f3f3f;
const double PI = acos(-1.0), eps = 1e-8;

int n, t, kcase;
int M, m[N], a[N], k;
/*
int mul(int a, int b, int mod)
{
    int res = 0;
    while(b) {
        if(b & 1) res = (res + a) % mod;
        a = (a + a) % mod;
        b >>= 1;
    }
    return res;
}*/

int mul(int x, int y, int p)
{
   
    int z = (long double)x / p * y;
    ll res = (unsigned long long)x * y - (unsigned long long) z * p;
    return (res + p) % p;
}

ll exgcd(int a, int b, int &x, int &y)
{
   
    if(b == 0) {
   
        x = 1, y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, x, y);
    ll z = x;
    x = y;
    y = z - y * (a / b);
    return d;
}

void solve()
{
   
    ll M = 1;
    scanf("%lld", &k);
    for(int i = 1; i <= k; ++ i) {
   
        scanf("%lld", &a[i]);
    }
    for(int i = 1; i <= k; ++ i) {
   
        scanf("%lld", &m[i]);
        M *= m[i];
    }
    for(int i = 1; i <= k; ++ i) a[i] = (a[i] % m[i] + m[i]) % m[i];
    ll res = 0;
    for(int i = 1; i <= k; ++ i) {
   
        ll Mi = M / m[i];
        ll ti, y;
        ll d = exgcd(Mi, m[i], ti, y);
        ti = (ti % m[i] + m[i]) % m[i];
        res += mul(mul(a[i], ti, M), Mi, M);
    }
    printf("%lld\n", (res % M + M) % M);
}

signed main()
{
   
    solve();
    return 0;
}

B、(P2480 [SDOI2010])古代猪文

Weblink

https://www.luogu.com.cn/problem/P2480

Problem

Solution

void 函数写成 ll 了,没有返回值 RE 了…

经典数论全家桶

懒得写题解了…


上述题解来源…

Code
简单实现一下就行了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef int itn;
const ll N = 5e5 + 7, p = 999911659, phi = 999911658, INF = 0x3f3f3f3f;
const double PI = acos(-1.0), eps = 1e-8;

ll val;
ll a[10];
ll M = 1;
ll fact[N];
ll infact[N];
ll n, g, t, cnt;
ll mo[10] = {
   0, 2, 3, 4679, 35617};

ll qpow(ll a, ll b, ll mod)
{
   
    ll res = 1;
    while(b) {
   
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
     }
     return res;
}

ll exgcd(ll a, ll b, ll &x, ll &y)
{
   
    if(b == 0) {
   
        x = 1, y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, x, y);
    ll z = x;x = y, y = z - y * (a / b);
    return d;
}

void init(ll p)
{
   
    fact[0] = infact[0] = 1;
    for(ll i = 1; i < p; ++ i) {
   
        fact[i] = fact[i - 1] * i % p;
        infact[i] = infact[i - 1] * qpow(i, p - 2, p) % p;
    }
}

ll C(ll a, ll b, ll p)
{
   
    if(a < b) return 0;
    return fact[a] * infact[b] % p * infact[a - b] % p;
}


ll lucas(ll a, ll b, ll p)
{
   
    if(b == 0) return 1;
    if(a < p && b < p) return C(a, b, p);
    return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}

ll CRT()
{
   
    ll res = 0;
    for(ll i = 1; i <= 4; ++ i) {
   
        ll Mi = M / mo[i];
        ll ti, y;
        ll d = exgcd(Mi, mo[i], ti, y);
        ti = (ti % mo[i] + mo[i]) % mo[i];
        res = (res + a[i] * ti % M * Mi % M) % M;
    }
    return (res % M + M) % M;
}

void solve()
{
   
    M = phi;
    scanf("%lld%lld", &n, &g);
    if(g % p == 0) {
   
        puts("0");
        return ;
    }

    for(ll k = 1; k <= 4; ++ k) {
   
        init(mo[k]);
        for(ll i = 1; i * i <= n; ++ i) {
   
            if(n % i == 0) {
   
                a[k] = (a[k] + lucas(n, i, mo[k])) % mo[k];
                if(i * i != n)
                    a[k] = (a[k] + lucas(n, n / i, mo[k])) % mo[k];
            }
        }
    }
    printf("%lld\n", qpow(g, CRT(), p));
}

int main()
{
   
    solve();
    return 0;
}

C、(P4774 [NOI2018]) 屠龙勇士

Weblink

https://www.luogu.com.cn/problem/P4774

Problem

Solution

可以直接用 multiset 代替平衡树,计算出每条龙所需要的剑,并且这把剑是一定能打败这条龙的,不然最后方程组无解,就会输出 -1,所以我们直接把打败龙的奖励放入 set 里,再选下一把剑。显然题目中的恢复机制我们可以得到一个同余方程:

C i x ≡ A i ( m o d    P i )

C i x A i ( mod P i )
CixAi(modPi)

题目数据不保证 m[i] 一定互质 ⇒ \Rightarrow excrt

一般的同余方程为:

x ≡ A i ( m o d    P i )

x A i ( mod P i )
xAi(modPi)

可以直接用拓展中国剩余定理。

但是本题的式子长这个样子:

C i x ≡ A i ( m o d    P i )

C i x A i ( mod P i )
CixAi(modPi)

考虑转成标准式子

C i x ≡ A i ( m o d    P i ) 显然有  C i x + P i y = A i exgcd解得 x’ y’ x = x ′ + k b d    = x ′ + k P i gcd ⁡ ( P i , C i ) ( m o d    P i gcd ⁡ ( P i , C i ) ) ⇒ x ≡ x ′ ( m o d    P i gcd ⁡ ( P i , C i ) )

C i x A i ( mod P i ) 显然有\  C i x + P i y = A i exgcd解得 x' y' x = x + k b d     = x + k P i gcd ( P i , C i ) ( mod P i gcd ( P i , C i ) ) x x ( mod P i gcd ( P i , C i ) )
CixAi(modPi)显然有 Cix+Piy=Aiexgcd解得 x’ y’x=x+kdb  =x+kgcd(Pi,Ci)Pi(modgcd(Pi,Ci)Pi)xx(modgcd(Pi,Ci)Pi)

拓展中国剩余定理求解即可。

注意特判 A i > P i A_i>P_i Ai>Pi 的情况即可,此时 P i = 1 P_i=1 Pi=1,答案显然就是 m a x { A [ i ] u s e [ i ] } max\{\cfrac{A[i]}{use[i]}\} max{ use[i]A[i]}

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef pair<int, int> PII;
const ll N = 5e6 + 7, INF = 0x3f3f3f3f;
const double PI = acos(-1.0), eps = 1e-8;

int n, m, k;
int A[N], P[N], award[N], atk[N], one;
multiset <int> st;
int C[N], use[N];
int ai[N], bi[N];

void init()
{
   
    one = true;
    st.clear();
    memset(use, 0, sizeof use);
}

int mul(int x, int y, int p)
{
   
    int z = (long double)x / p * y;
    int res = (unsigned long long)x * y - (unsigned long long) z * p;
    return (res + p) % p;
}

int exgcd(int a, int b, int &x, int &y)
{
   
    if(b == 0) {
   
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, x, y);
    int z = x;
    x = y, y = z - (a / b) * y;
    return d;
}

int excrt()
{
   
    int x, y, k;
    int M = bi[1], ans = ai[1];
    for(int i = 2; i <= n; ++ i) {
   
        int a = M, b = bi[i], c = (ai[i] - ans % b + b) % b;
        int d = exgcd(a, b, x, y);
        int bg = b / d;
        if(c % d != 0) return -1;
        x = mul(x, c / d, bg);
        ans += x * M;
        M *= bg;
        ans = (ans % M + M) % M;
    }
    return (ans % M + M) % M;
}

void sp_work()
{
   
    int ans = - INF;
    for(int i = 1; i <= n; ++ i) {
   
        if(use[i]) {
   
            ans = max(ans, (int)ceil((double)A[i] / use[i]));
        }
    }
    printf("%lld\n", ans);
}

bool work()
{
   
    int x, y;
    for(int i = 1; i <= n; ++ i) {
   
        int a = use[i], b = P[i], c = A[i];
        int d = exgcd(a, b, x, y), bg = b / d;
        if(c % d != 0)
            return false;
        x = mul(x, c / d, bg);
        x = (x % b + b) % b;
        ai[i] = x;
        bi[i] = bg;
     }
     return true;
}

void solve()
{
   
    init();
    scanf("%lld%lld", &n, &m);
    for(int i = 1; i <= n; ++ i) scanf("%lld", &A[i]);
    for(int i = 1; i <= n; ++ i) {
   
        scanf("%lld", &P[i]);
        if(A[i] > P[i])
            one = false;
    }
    for(int i = 1; i <= n; ++ i) scanf("%lld", &award[i]);
    for(int i = 1; i <= m; ++ i) {
   
        scanf("%lld", &atk[i]);
        st.insert(atk[i]);
    }

    for(int i = 1; i <= n; ++ i) {
   
        multiset <int> :: iterator it;
        if(A[i] < *st.begin()) it = st.begin();
        else it =  -- st.upper_bound(A[i]);
        use[i] = *it;
        st.insert(award[i]);
        st.erase(it);
    }
    if(one == false) {
   
        sp_work();
        return ;
    }
    bool win = work();
    if(win == 0) {
   
        puts("-1");
        return ;
    }

    printf("%lld\n", excrt());
    return ;
}


signed main()
{
   
    int t;
    scanf("%lld", &t);
    while(t -- ) {
   
        solve();
    }
    return 0;
}

/*
487472861809
3871865111
7560798679
584853762636
670310334583
*/

D、(Codeforces 707Div. 2) D - Two chandeliers

Weblink

https://codeforces.com/contest/1501/problem/D

Solution 1 二分 + 中国剩余定理

Solution 2 二分 + 拓展欧几里德

E、(2018 CCPC-Final K)Mr. Panda and Kakin

Weblink

https://codeforc.es/gym/102055/problem/K

Problem

Translation

给出 n , c n , c n,c n = p ∗ q n = p ∗ q n=pq p p p q q q x x x 附近相邻的两个质数, c = f 2 30 + 3   m o d   n c = f^{2^{30}+3} \ mod\ n c=f230+3 mod n。求出 f f f 的值。

Solution 1 中国剩余定理

Solution 2 拓展欧几里德

F、(2019 ICPC 徐州网络赛 A) Who is better?

Weblink

https://nanti.jisuanke.com/t/41383

Problem

Solution

斐波那契博弈 + 拓展中国剩余定理

…就是个板子题,只不过凉心数据会爆 long long…题目里也不给数据范围就离谱, n ≤ 1 0 15 n\le 10^{15} n1015

#include <bits/stdc++.h>

using namespace std;

const int N = 5000007;
#define ll long long
#define LL __int128 

LL n; 
ll ai[N], bi[N];
LL f[N];

LL exgcd(LL a, LL b, LL &x, LL &y)
{
   
	if(b == 0) {
   
		x = 1, y = 0;
		return a;
	}
	LL d = exgcd(b, a % b, x, y);
	LL z = x;
	x = y; y = z - a / b * y;
	return d;
}

LL mul(LL a, LL b, LL c)
{
   
	LL res = 0;
	while(b) {
   
		if(b & 1) res = (res + a) % c;
		a = (a + a) % c;
		b >>= 1;
	}
	return res;
}

LL EXCRT(int n)
{
   
	LL x, y, k;
	LL M = bi[1];
    LL ans = ai[1];
	for(int i = 2; i <= n; ++ i) {
   
		LL a = M, b = bi[i], c = (ai[i] - ans % b + b) % b;
		LL d = exgcd(a, b, x, y);
		LL bg = b / d;
		if(c % d != 0) return -1;
		x = mul(x, c / d, bg);
		ans += x * M;
		M *= bg;
		ans = (ans % M + M) % M;
	}
	return (ans % M + M) % M;
}

//n = b (mod a)
//b -> ai(常数), a -> bi(模数)
ll k;

int main()
{
   
	scanf("%lld", &k);
	for(ll i = 1; i <= k; ++ i) {
   
		scanf("%lld%lld", &bi[i], &ai[i]);
	}
	n = EXCRT(k);
	if(n == -1) {
   
		puts("Tankernb!");
		return 0;
	} 
	f[1] = f[2] = 1;
	for(int i = 3; i <= 75; ++ i) {
   
		f[i] = f[i - 1] + f[i - 2];
		
	} 
	for(int i = 1; i <= 75; ++ i) {
   
		if(f[i] == n) {
   
			puts("Lbnb!");
			return 0;
		}
	}
	puts("Zgxnb!");
	return 0;
}

转载:https://blog.csdn.net/weixin_45697774/article/details/114868363
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场