同余系基本定理2
command_block · · 个人记录
上回请看 同余系基本定理1
默认大家已经对同余系有一定的了解,而且能够熟练运用基础算法。
-
P1495 【模板】中国剩余定理(CRT)
有若干个同余方程
满足
考虑把这些方程两两合并。
先看两个方程
可以构造
问题在于,我们希望得到
合并完之后的模数是
边界方程可以视作
复杂度
#include<cstdio>
#define ll long long
using namespace std;
void exgcd(ll a,ll b,ll &x,ll &y){
if (b==0){x=1;y=0;return ;}
exgcd(b,a%b,y,x);y-=(a/b)*x;
}
ll inv(ll a,ll m){
ll x,y;exgcd(a,m,x,y);
return (x%m+m)%m;
}
int n;
ll c,p,ret,mp;
int main()
{
scanf("%d",&n);
ret=0;mp=1;
for (int i=1;i<=n;i++){
scanf("%lld%lld",&p,&c);
ll sp=mp*p;
ret=(ret*inv(p,mp)%sp*p+mp*inv(mp,p)%sp*c)%sp;
mp=sp;
}printf("%lld",ret);
return 0;
}
-
例题 : P3868 [TJOI2009]猜数字
-
P3807 【模板】卢卡斯定理
构造
-
对于
1\leq i\leq p-1 ,总有\binom{p}{i}=0\pmod p .原因是
\dbinom{p}{i}=\dfrac{p!}{(p-i)!i!} ,而p 是素数,所以(p-i)! 和i! 都不含因子p ,无法抵消p! 中的恰一个因子p .
那么可以得到
能进一步推出
我们把
然后考虑
提取第
由于乘积项的次数不交,可以把
“不交”的意思是某两位之间不会互相影响。因为将一个
即 : 把
#include<cstdio>
#define MaxN 100500
#define ll long long
using namespace std;
int mod;
ll powM(ll a,int t=mod-2){
ll ret=1;
while(t){
if (t&1)ret=ret*a%mod;
a=a*a%mod;t>>=1;
}return ret;
}
ll fac[MaxN],ifac[MaxN];
ll sC(int n,int m){
if (n<m)return 0;
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
ll C(ll n,ll m){
if (!m)return 1;
return sC(n%mod,m%mod)*C(n/mod,m/mod)%mod;
}
void Init()
{
fac[0]=1;
for (int i=1;i<mod;i++)
fac[i]=fac[i-1]*i%mod;
ifac[mod-1]=powM(fac[mod-1]);
for (int i=mod-1;i;i--)
ifac[i-1]=ifac[i]*i%mod;
}
void solve()
{
ll n,m;
scanf("%lld%lld%d",&n,&m,&mod);
m+=n;Init();
printf("%lld\n",C(m,n));
}
int main()
{
int T;scanf("%d",&T);
while(T--)solve();
return 0;
}
-
例题 P4345 [SHOI2015]超能粒子炮·改
题意 : 多次给出
n,k ,求\sum\limits_{i=0}^k\dbinom{n}{i}\pmod {2333}
设
根据卢卡斯定理有
前半部分是块状变化的,我们分别枚举
注意到
设
则有
先预处理
然后就是求解 Lucas定理即可做到
观察参数
然后由于
复杂度
提交记录
- 周边 : Kummer定理
则因子
当某一位上
那么只有
-
例题 : CF582D Number of Binominal Coefficients
题意 : 给出参数
lim,p,a ,求有多少个组合数\dbinom{n}{k} 是p^a 的倍数,且0\leq k \leq n \leq lim .
根据 Kummer 定理,能将问题转化成这样的形式:
求两个数
这看起来就很数位DP的样子。
设
注意这里是不需要记录前导
转移的时候分几类讨论 : (下一位进/不进位)(这一位进/不进位),分别计算可能的数对个数即可。
复杂度
-
中段综合测试 : P2480 [SDOI2010]古代猪文
题意 : 给出
n,g ,求g^{\small\sum\limits_{k|n}\binom{n}{k}} ,对999911659 取模。
首先,模数是个质数,根据费马小定理,指数要对
则对
观察到没有重复的素因子,这比较和善。
求出分别模 Exgcd)。
求组合数可以使用Lucas定理。
复杂度是
-
扩展欧拉定理(降幂塔)
上集我们已经证明了 :
现在我们来看扩展形式,
第一种情况已被证明,第二种情况是显然的,现在我们来证明第三种情况。
说人话就是 : 当
不妨先考虑
此时若有
那么根据普通欧拉定理有
于是有
两边同时乘以
注意到
所以有
结论同时也对于
若有
则
有可能会出现
然后对
现在我们对任意的
然后使用唯一分解定理即可把结论扩展至全体正整数。
-
例题 : P4139 上帝与集合的正确用法
题意 : 求
2^{2^{2^{2^{\dots}}}}\bmod m ,\ p\leq 10^7
设
根据扩展欧拉定理能得到
边界就是
取多少次
注意到欧拉函数的其中一个定义式 :
当
当
综上,经过
线性筛出欧拉函数复杂度是
提交记录
-
附加题 :
- P4861 按钮
若
\gcd(k,m)>1 ,显然无解。否则满足经典欧拉定理,则有
k^{\varphi(m)}=1\pmod m ,但是\varphi(m) 并不是答案,可能存在更小的循环节。可能的循环节一定是
\varphi(m) 的约数,否则能导出矛盾。于是只需要判定
\varphi(m) 的约数即可,复杂度为O(\sqrt{m}\log m) 。[提交记录] ()
- P3934 Nephren Ruq Insania
考虑乘方降幂塔,在
O(\log p) 层之后模数就会变为1 ,所以我们只需要暴力取出l 后的若干项即可。上一题中,由于
2^{2^{2^{2^{\dots}}}} 恒大于\varphi(m) ,所以总可以使用第三种情况。现在由于可能产生第二种情况,我们计算时需要按照如下原则:
如果算的的数大于
m 则替换成(a\bmod m)+m ,否则不变。提交记录
-
CF906D Power Tower : 上一题的静态版,但数据较强。
-
P3747 [六省联考2017]相逢是问候
仍然考虑结论 : 降幂塔不超过
O(\log p) 层。某个位置被赋值O(\log p) 次之后就必然变为c^{c^{c^{\dots}}}\bmod p 。考虑把不再变化的一整个区间冻结,可以证明只会产生
O(n\log p) 次修改叶子的操作。每次单点修改需要计算一次降幂塔,朴素实现复杂度是
O(\log p^2) 的,无法通过。模数只有
O(\log p) 个,可以分别于处理c 的光速幂。总复杂度为
O(\sqrt{p}\log p+m\log ^2p) 。提交记录
- P5240 Derivation
-
P4777 【模板】扩展中国剩余定理(EXCRT)
仍然是若干个方程组
求
仍然考虑两两合并
原先我们要直接构造某个模数在另一个模数下的逆,现在似乎不太行了。
第一个方程的通解为
移项有
等价于
根据斐蜀定理,方程有解的充要条件是
如果可解,先扩欧求出
注意,新的模数是
复杂度仍然是
#include<cstdio>
#define ll long long
using namespace std;
inline ll mul(ll a,ll b,ll m){
ll d=((long double)a/m*b+0.5);
ll r=a*b-d*m;
return r<0?r+m:r;
}
ll gcd(ll a,ll b)
{return !b ? a : gcd(b,a%b);}
void exgcd(ll a,ll b,ll &x,ll &y){
if (b==0){x=1;y=0;return ;}
exgcd(b,a%b,y,x);y-=(a/b)*x;
}
int n;
ll c1,p1,c2,p2;
int main()
{
scanf("%d",&n);
c1=0;p1=1;
for (int i=1;i<=n;i++){
scanf("%lld%lld",&p2,&c2);
ll pd=gcd(p1,p2),sp=p2/pd*p1,k,y;
c2=(c2-c1%p2+p2)%p2;
//if (c2%pd) No Sol;
exgcd(p1,p2,k,y);
k=mul(k,(c2/pd),p2);
c1=(c1+mul(p1,k,sp))%sp;
p1=sp;
}printf("%lld",c1);
return 0;
}
- 例题 : P4774 [NOI2018]屠龙勇士
发现不会做这道题是写本文的动力之一。
首先容易发现,屠龙的规则和顺序严格确定,那么如果我们能成功,每一轮使用的剑是相同的。拿multiset维护一下便可。
设杀第
击杀每条龙的条件很像取模,我们能列出式子
问题在于,可能有
考虑对每条龙记录砍到负数的最小次数的最大值,最后
接下来的问题就是解方程组
考虑将
先特判
由于
不过,我们仍然能尝试求解
否则由斐蜀定理得
则有
我们对
由于
剩下的就是一个扩展中国剩余定理。
#include<algorithm>
#include<cstdio>
#include<set>
#define Itor set<ll>::iterator
#define ll long long
#define MaxN 100500
using namespace std;
const int mod=998244353;
ll mul(ll a,ll b,ll m)
{return (((a>>20)*b%m<<20)+(a-(a>>20<<20))*b)%m;}
ll gcd(ll a,ll b)
{return !b ? a : gcd(b,a%b);}
ll lcm(ll a,ll b)
{return a/gcd(a,b)*b;}
void exgcd(ll a,ll b,ll &x,ll &y){
if (!b){x=1;y=0;return ;}
exgcd(b,a%b,y,x);y-=(a/b)*x;
}
ll inv(ll a,ll m){
ll x,y;exgcd(a,m,x,y);
return (x+m)%m;
}
bool fl;
void merge(ll &c1,ll &p1,ll c2,ll p2)
{
ll c=(c2-c1%p2+p2)%p2,d=gcd(p1,p2);
if (c%d){puts("-1");fl=1;return ;}
ll k,y;exgcd(p1,p2,k,y);
ll p=lcm(p1,p2);
c1=(c1+mul(mul(k,p1,p),c/d,p))%p;
p1=p;
}
multiset<ll> s;
ll a[MaxN],k[MaxN],p[MaxN],t[MaxN]
,mx,tc,tp;
void calc(ll k,ll c,ll p)
{
mx=max(mx,(c-1)/k+1);
if (k%p==0){
if (c%p==0)return ;
else {puts("-1");fl=1;return ;}
}
ll d=gcd(k,p);
if (c%d){puts("-1");fl=1;return ;}
k/=d;p/=d;c/=d;
c=mul(c,inv(k,p),p);
merge(tc,tp,c,p);
}
int n,m;
void solve()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)scanf("%lld",&a[i]);
for (int i=1;i<=n;i++)scanf("%lld",&p[i]);
for (int i=1;i<=n;i++)scanf("%lld",&t[i]);
s.clear();
for (int i=1;i<=m;i++){
ll x;scanf("%lld",&x);
s.insert(x);
}
for (int i=1;i<=n;i++){
Itor it=s.upper_bound(a[i]);
if (it!=s.begin())it--;
k[i]=*it;s.erase(it);
s.insert(t[i]);
}
mx=0;tp=1;tc=0;fl=0;
for (int i=1;i<=n&&!fl;i++)
calc(k[i],a[i],p[i]);
if (fl)return ;
ll tk=mx/tp;
while(tk*tp+tc<mx)tk++;
printf("%lld\n",tk*tp+tc);
}
int main()
{
int T;scanf("%d",&T);
while(T--)solve();
return 0;
}
-
P4720 【模板】扩展卢卡斯
跟普通卢卡斯定理关系不大 (
我们把模数分解成
考虑
但这毫无作用,往往有
考虑将
则有
我们计算
上文介绍了
对于
变为
对于中间的每一块,
这样的段会有
具体见代码,注意散块的边界情况。这样计算一次的复杂度是
对于那些为
于是就变为求解
预处理复杂度为
( 如果采用欧拉定理+光速幂可以达到
#include<algorithm>
#include<cstdio>
#define MaxN 1005000
#define ll long long
using namespace std;
void exgcd(ll a,ll b,ll &x,ll &y){
if (b==0){x=1;y=0;return ;}
exgcd(b,a%b,y,x);y-=(a/b)*x;
}
ll inv(ll a,ll m){
ll x,y;exgcd(a,m,x,y);
return (x%m+m)%m;
}
ll powM(ll a,ll t,int mod)
{
ll ret=1;
while(t){
if (t&1)ret=ret*a%mod;
a=a*a%mod;t>>=1;
}return ret;
}
ll v(ll n,int p){
ll ret=0;
while(n)ret+=(n/=p);
return ret;
}
ll sav[MaxN];
ll r(ll n,int p,int mod){
if (n==0)return 1;
return powM(sav[mod-1],n/mod,mod)*sav[n%mod]%mod*r(n/p,p,mod)%mod;
}
ll C(ll n,ll m,int p,int mod)
{
int c=v(n,p)-v(m,p)-v(n-m,p);
ll ans=powM(p,c,mod);
if (!ans)return 0;
sav[0]=1;
for (int i=1;i<mod;i++)
if (i%p==0)sav[i]=sav[i-1];
else sav[i]=sav[i-1]*i%mod;
ans=ans*r(n,p,mod)%mod;
ans=ans*inv(r(m,p,mod),mod)%mod;
ans=ans*inv(r(n-m,p,mod),mod)%mod;
return ans;
}
ll ret,mp=1;
void add(ll c,ll p)
{
ll sp=mp*p;
ret=(ret*inv(p,mp)%sp*p+mp*inv(mp,p)%sp*c)%sp;
mp=sp;
}
ll n,m;int p;
int main()
{
scanf("%lld%lld%lld",&n,&m,&p);
int d=2;
while(d*d<=p){
if (p%d==0){
int mod=1;
while(p%d==0){
p/=d;
mod*=d;
}add(C(n,m,d,mod),mod);
}d++;
}if (p>1)add(C(n,m,p,p),p);
printf("%lld",ret);
return 0;
}
-
例题① : P2183 [国家集训队]礼物
题意 : 有
n 个不同的小球,投入m 个盒子里面,钦定第i 个盒子要投w_i 个球,问方案数。对
mod 取模,保证mod\leq 10^9 且分解之后的每个p^c\leq 10^5
先判掉无解的情况。设
不难发现题目叫我们求的是
也就是在固定模数下求解EXLucas即可。
理论复杂度为
评测记录
-
例题② : P3726 [AH2017/HNOI2017]抛硬币
题解Link