参考大神
问题
对于一系列 x m o d m i = a i x\ mod\ m_i = a_i x mod mi=ai,求解最小的 x x x。
解法
首先要保证 m i m_i mi互质, m u l mul mul表示 m i m_i mi的乘积, M i = m u l m i M_i=\frac{mul}{m_i} Mi=mimul, M i ∗ t i = 1 ( m o d m i ) M_i*t_i=1(mod\ m_i) Mi∗ti=1(mod mi)
从而可以构造出一组解,
x
0
=
∑
a
i
M
i
t
i
x_0=\sum a_iM_it_i
x0=∑aiMiti,通解即
x
=
x
0
+
k
∗
m
u
l
x=x_0+k*mul
x=x0+k∗mul
所以最小即
x
0
%
m
u
l
x_0\%mul
x0%mul
这里 x 0 x_0 x0中 M i t i M_it_i Miti不是 1 1 1,因为不处于 m i m_i mi的剩余系了,而是实数系。
求逆元的时候,我们用的是扩展欧几里得。相关证明在参考处。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f3f3f3f3f
typedef long long ll;
using namespace std;
const int N = 2e5+500;
const int maxn=2e5+500;
const int mod = 1000007;
ll a[20],m[20],Mi[20];//a是余数,m是模数
void exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){x=1,y=0;return ;}
exgcd(b,a%b,x,y);
ll z=x;x=y,y=z-y*(a/b);
}
void get_a(){
}
ll crt(int n){
ll mul=1,ret=0;
for(int i=1;i<=n;i++)mul*=m[i];
for(int i=1;i<=n;i++){
Mi[i]=mul/m[i];
ll x=0,y=0;
exgcd(Mi[i],m[i],x,y);
ret+=a[i]*Mi[i]*(x<0?x+m[i]:x);
}
return ret%mul;
}
int main(){
int n;cin>>n;
FOR(i,1,n)scanf("%lld%lld",&m[i],&a[i]);
ll ans=crt(n);
cout<<ans<<endl;
}
应用
对存在逆元的非质数取模问题
模板如下:
ll a[2000],m[2000],Mi[2000];//a是余数,m是模数
bool ok=false;
void exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){x=1,y=0;return ;}
exgcd(b,a%b,x,y);
ll z=x;x=y,y=z-y*(a/b);
}
//对于给定的x,对mi取模,再反解最小x,对P取模即对非质数取模方法。
void get_a(ll x,ll y){
for(int i=1;i<=4;i++)a[i]=ls[i].Combination(x,y);
}
ll crt(int n){
ll mul=1,ret=0;
for(int i=1;i<=n;i++)mul*=m[i];
for(int i=1;i<=n;i++){
Mi[i]=mul/m[i];
ll x=0,y=0;
exgcd(Mi[i],m[i],x,y);
ret+=a[i]*Mi[i]*(x<0?x+m[i]:x);
}
return ret%mul;
}
ll get(ll x,ll y){
if(ok){
return ls[0].Combination(x,y)%MOD;
}
else{
get_a(x,y);
return crt(4)%MOD;
}
}
做法很简单,就是讲模数分解质因子。
这里不考虑质因子有次方的情况,然后对于每个因子取模得到一个值
a
i
a_i
ai。
即我们去求最小解
x
x
x 满足
x
m
o
d
m
i
=
a
i
x\ mod\ m_i=\ a_i
x mod mi= ai。
得到这个
x
x
x之后,对
P
P
P取模即是答案。
这份模板中
g
e
t
_
a
get\_a
get_a函数就是取模的过程,因为这是组合数的模板,所以…懒得改了先。
例题——洛谷P4478 上学路线
题意
网格图,从
(
0
,
0
)
(0,0)
(0,0)到
(
n
,
m
)
(n,m)
(n,m),要求不走到某些点,多少种方案。
只能走右和上,这些点给出。
题解
不知道为什么没想出来。
首先
C
(
n
,
m
)
C(n,m)
C(n,m)即是总方案,去掉那些走到那些点的方案。
枚举走了哪些点有点不太靠谱,但是枚举第一次走到哪个点是可行的。
d
p
[
i
]
dp[i]
dp[i]表示第一次到第
i
i
i个点的方案数
d
p
[
i
]
=
C
(
x
i
+
y
i
,
x
i
)
−
∑
d
p
[
j
]
∗
C
(
d
i
f
x
+
d
i
f
y
,
d
i
f
x
)
dp[i]=C(x_i+y_i,x_i)-\sum dp[j]*C(difx+dify,difx)
dp[i]=C(xi+yi,xi)−∑dp[j]∗C(difx+dify,difx)
即,总方案减去,枚举走到不该走的点的方案中走的第一个点,一旦走了这第一个点,其余怎么走都是不合法的,所以直接计算还差多少即可。
为了保证
d
p
dp
dp的合法性,要事先进行排序,保证所有在
i
i
i左下的点
j
j
j都
<
i
<i
<i。
最后答案是 d p [ T + 1 ] dp[T+1] dp[T+1],因为总共是 T T T个关键点,而我们要走到终点。
其次就是取模问题,给的模数由于是非质数和质数,所以需要 c r t crt crt合并一下。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f3f3f3f3f
typedef long long ll;
using namespace std;
const int maxn=1e6+500;
ll N,M,MOD;
int T;
struct Lucas{
ll ff[maxn+5],mod;
void init(){
ff[0]=1;
FOR(i,1,maxn)ff[i]=(ff[i-1]*i)%mod;//处理P大小
}
ll gcd(ll a,ll b)
{
if(b==0)return a;
else return gcd(b,a%b);
}
ll x,y;
void Extended_gcd(ll a,ll b)
{
if(b==0)
{
x=1;
y=0;
}
else
{
Extended_gcd(b,a%b);
ll t=x;
x=y;
y=t-(a/b)*y;
}
}
//计算不大的C(n,m)
ll C(ll a,ll b)
{
if(b>a)return 0;
b=(ff[a-b]*ff[b])%mod;
a=ff[a];
ll c=gcd(a,b);
a/=c;b/=c;
Extended_gcd(b,mod);
x=(x+mod)%mod;
x=(x*a)%mod;
return x;
}
//Lucas定理
ll Combination(ll n, ll m)
{
ll ans=1;
ll a,b;
while(m||n)
{
a=n%mod;
b=m%mod;
n/=mod;
m/=mod;
ans=(ans*C(a,b))%mod;
}
return ans;
}
}ls[5];
ll a[2000],m[2000],Mi[2000];//a是余数,m是模数
bool ok=false;
void exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){x=1,y=0;return ;}
exgcd(b,a%b,x,y);
ll z=x;x=y,y=z-y*(a/b);
}
//对于给定的x,对mi取模,再反解最小x,对P取模即对非质数取模方法。
void get_a(ll x,ll y){
for(int i=1;i<=4;i++)a[i]=ls[i].Combination(x,y);
}
ll crt(int n){
ll mul=1,ret=0;
for(int i=1;i<=n;i++)mul*=m[i];
for(int i=1;i<=n;i++){
Mi[i]=mul/m[i];
ll x=0,y=0;
exgcd(Mi[i],m[i],x,y);
ret+=a[i]*Mi[i]*(x<0?x+m[i]:x);
}
return ret%mul;
}
ll get(ll x,ll y){
if(ok){
return ls[0].Combination(x,y)%MOD;
}
else{
get_a(x,y);
return crt(4)%MOD;
}
}
ll dp[maxn];
struct node{ll x,y;}A[maxn];
int main(){
cin>>N>>M>>T>>MOD;
// scanf("%d%d%d%lld",&N,&M,&T,&MOD);
ls[0].mod=1000003,m[1]=ls[1].mod=3,m[2]=ls[2].mod=5,m[3]=ls[3].mod=6793,m[4]=ls[4].mod=10007;
for(int i=0;i<=4;i++)ls[i].init();
if(MOD==1000003)ok=true;
for(int i=1;i<=T;i++)scanf("%d%d",&A[i].x,&A[i].y);
T++,A[T].x=N,A[T].y=M;
sort(A+1,A+1+T,[](node a,node b){
if(a.x==b.x)return a.y<b.y;
return a.x<b.x;
});
for(int i=1;i<=T;i++){
ll ret=get(A[i].x+A[i].y,A[i].x);
for(int j=1;j<i;j++){
if(A[i].x>=A[j].x&&A[i].y>=A[j].y)ret=((ret-dp[j]*get(A[i].x-A[j].x+A[i].y-A[j].y,A[i].x-A[j].x))%MOD+MOD)%MOD;
}
dp[i]=ret%MOD;
}
cout<<dp[T];
}