zoukankan      html  css  js  c++  java
  • 【笔记】 exlucas

    扩展 lucas

    (inom{n}{m} mod p),其中 (1 le m le n le 10^{18},2 le p le 10^6)

    首先我们可以对 (p) 质因数分解 (p=prod p_i^{a_i}),对于每一个质因数我们求出答案,然后用 CRT 合并。

    单独考虑每一个模数 (p^k)

    我们要求解 (inom{n}{m} mod p^k),其中 (1 le m le n le 10^{18},2 le p le 10^6)(p) 为质数。

    障碍

    找找是什么阻挡了我们求解的脚步?原因是 ((n-m)!m!) 有可能找不到逆元。怎么解决呢?

    天真的我曾经以为,当 (n) 足够大的时候,(inom{n}{m} mod p^k=0),原因是我觉得 (n) 会有很多 (p) 的因子,于是乎 (mod p) 完之后就成了 (0)

    且不论这个想法是否正确,至少它指明了一个方向:求出 (inom{n}{m}) 有多少个 (p) 的因子。

    [inom{n}{m} = dfrac{n!}{(n-m)!m!}=dfrac{frac{n!}{p^x}}{frac{(n-m)!}{p^y} imes frac{m!}{p^z}} p^{x-y-z} ]

    这样子,前面这部分就不含 (p) 的因子,就可以求出逆元了。

    于是我们现在的问题分如下两部分:

    1. 求解 (n!) 有多少个 (p) 的因子;
    2. 求解 (n!) 去除 (p) 的因子后的值 (mod p^k)

    Task1

    不难把 (n!) 拆解成如下部分:

    [n!equiv p^{lfloor frac{n}{p} floor}prod_{i=1}^{lfloor frac{n}{p} floor}i imes left(prod_{i=1land i otequiv0pmod p}^{p^k}i ight)^{lfloor frac{n}{p^k} floor} left( prod_{i=p^k+1land i otequiv0 pmod p}^{n}i ight) pmod{p^k} ]

    后面两个带括号的部分都不带 (p) 因子,前面 (p^{lfloor frac{n}{p} floor}) 贡献了 (lfloor frac{n}{p} floor)(prod_{i=1}^{lfloor frac{n}{p} floor}i) 是一个阶乘,可以递归下去。

    不难发现可以 (O(log_p n)) 计算 (p) 的指数。

    Task2

    按照 Task1 的式子计算。

    不难发现,预处理一下 (prod_{i=1land i otequiv0pmod p}^{p^k}i) 这个前缀积之后就又可以递归计算了,复杂度 (O(log_p n log n)),多出来一个 (log n) 是因为快速幂,如果需要多次计算组合数的话,可以预处理一下次幂,实现 (O(1)) 快速幂。


    计算 (dfrac{frac{n!}{p^x}}{frac{(n-m)!}{p^y} imes frac{m!}{p^z}} p^{x-y-z}) 时要用 exgcd 求逆元,或者算 (varphi) 求快速幂。

    最后用 CRT 把各个质数次方的答案合并即可。

    Code

    #include <bits/stdc++.h>
    
    #define debug(...) fprintf(stderr ,__VA_ARGS__)
    #define __FILE(x)
    	freopen(#x".in" ,"r" ,stdin);
    	freopen(#x".out" ,"w" ,stdout)
    #define LL long long
    
    const int MX = 1e6 + 23;
    
    LL read(){
    	char k = getchar(); LL x = 0;
    	while(k < '0' || k > '9') k = getchar();
    	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
    	return x;
    }
    
    LL qpow(LL a ,LL b ,LL p){
    	LL ans = 1;
    	while(b){if(b & 1) ans = ans * a % p;
    		a = a * a % p ,b >>= 1;
    	}return ans;
    }
    
    void exgcd(LL a ,LL b ,LL &x ,LL &y){
    	if(!b) return x = 1 ,y = 0 ,void();
    	exgcd(b ,a % b ,y ,x); y -= a / b * x;
    }
    
    LL inv(LL x ,LL p){
    	LL ret ,tmp;
    	exgcd(x ,p ,ret ,tmp);
    	return (ret + p) % p;
    }
    
    LL rfac[MX];
    LL other(LL n ,LL p ,LL pk){
    	if(n == 0) return 1LL;
    	LL ans = other(n / p ,p ,pk);
    	ans = ans * qpow(rfac[pk] ,n / pk ,pk) % pk;
    	ans = ans * rfac[n % pk] % pk;
    	return ans;
    }
    
    LL calcexp(LL n ,LL p){
    	if(n < p) return 0LL;
    	return n / p + calcexp(n / p ,p);
    }
    
    void prework(LL p ,LL pk){
    	rfac[0] = 1;
    	for(int i = 1 ; i <= pk ; ++i){
    		if(i % p == 0) rfac[i] = rfac[i - 1];
    		else rfac[i] = rfac[i - 1] * i % pk;
    	}
    }
    
    LL a[MX] ,xp[MX] ,cnt;
    LL b[MX];
    
    LL exlucas(LL n ,LL m ,LL p){
    	int f = p;
    	for(int i = 2 ; i * i <= f ; ++i){
    		if(f % i) continue;
    		a[++cnt] = i;
    		xp[cnt] = 1;
    		while(f % i == 0){
    			f /= i;
    			xp[cnt] *= i;
    		}
    	}
    	if(f != 1) a[++cnt] = f ,xp[cnt] = f;
    	for(int u = 1 ; u <= cnt ; ++u){
    		LL P = a[u] ,PK = xp[u];
    		prework(P ,PK);
    		LL ex = calcexp(n ,P) - calcexp(m ,P) - calcexp(n - m ,P);
    	   	LL A = other(n ,P ,PK) * inv(other(m ,P ,PK) ,PK) % PK * inv(other(n - m ,P ,PK) ,PK) % PK;
    		b[u] = qpow(P ,ex ,PK) * A % PK;
    	}
    
    	LL ans = 0;
    	for(int u = 1 ; u <= cnt ; ++u){
    		ans = (ans + (p / xp[u]) * inv(p / xp[u] ,xp[u]) % p * b[u]) % p;
    	}
    	return ans;
    }
    
    int main(){
    	LL n = read() ,m = read() ,p = read();
    	printf("%lld
    " ,exlucas(n ,m ,p));
    	return 0;
    }
    
  • 相关阅读:
    一句话说说java设计模式
    说说JVM中的操作码
    说说javap命令
    java中的类加载器
    浅谈幂等
    InstallShield Limited Edition for Visual Studio 国内注册时国家无下拉框解决方法
    Winform程序部署方式总结二——Windows Installer发布
    Winform程序部署方式总结一——ClickOnce发布
    WebService部署服务器调试时提示 “测试窗体只能用于来自本地计算机的请求”解决方法
    MVC绕过登陆界面验证时HttpContext.Current.User.Identity.Name取值为空问题解决方法
  • 原文地址:https://www.cnblogs.com/imakf/p/14317990.html
Copyright © 2011-2022 走看看