题目大意
题目很长,这里就不说了吧。粘个链接:古代猪文
题目大意就是:给定整数n,q,计算
q
∑
d
∣
n
C
n
d
m
o
d
999911659
q^{\sum_{d|n} C_{n}^{d}}\ mod\ 999911659
q∑d∣nCnd mod 999911659.
题解
这道题的 n n n很大;如果暴力枚举每一个 d d d,再直接求解组合数的话,由于 n n n和 d d d很大,在计算组合数的时候会浪费很大的时间,因此我们需要对这一个算法进行优化。
在讨论本题之前我们先来讨论一下 L u c a s Lucas Lucas定理以及其实现过程。
- 对于任意质数P,有: C n m ≡ C n / P m / P ∗ C n % P m % P , ( m o d p ) C_{n}^{m}\ \equiv\ C_{n/P}^{m/P}*C_{n\%P}^{m\%P},(mod\ p) Cnm ≡ Cn/Pm/P∗Cn%Pm%P,(mod p)
- 证明过程很复杂,我们在这里不详细讨论。我们主要考虑如何运用到组合数的计算内。
- 对于利用
L
u
c
a
s
Lucas
Lucas定理求解组合数,我们可以使用递归来进行求解,以求解
C
n
m
C_{n}^{m}
Cnm即:
当 n = m n=m n=m时返回1;若 n n n和 m m m都同时小于模数p,我们直接暴力计算组合数即可;否则我们就必须使用 L u c a s Lucas Lucas定理来进行递归实现。 - 友情提醒:必须要小于质数 p p p才能提前计算而不能小于某一个预先处理好的大数字;因为当这一个大数字超过 p p p时就会出现大量模 p p p等于 0 0 0的情况,那么你就会发现组合数计算出来以后全部都是0.但是一般情况下因为p比较大所以不存在任何问题;基础这道题的特殊性,模数比较小,组合数的计算上就会出现问题了。
- 代码如下:你可以将 p [ k ] p[k] p[k]理解一个取模的质数; p o w e r power power是一个快速幂函数,用于求解乘法逆元。
LL C(LL n,LL m,LL k)
{
if (n == m) return 1;
if (n < m) return 0;
if (n<p[k] && m<p[k])
{
LL sum1 = jc[n][k];
LL sum2 = jc[n-m][k]*jc[m][k]%p[k];
return sum1*power(sum2,p[k]-2,p[k])%p[k];
}
return C(n/p[k],m/p[k],k)*C(n%p[k],m%p[k],k)%p[k];
}
OK,说完卢卡斯以后回归本题。
首先我们需要特判一种特殊的情况,当q是999911659=P的倍数时,答案为0.
若不是上面的情况,我们可以根据欧拉定理推论:
a
b
≡
a
b
%
p
h
i
(
p
)
(
m
o
d
p
)
a^b\ \equiv\ a^{b\%phi(p)}(mod\ p)
ab ≡ ab%phi(p)(mod p),得到:
q
∑
d
∣
n
C
n
d
≡
q
∑
d
∣
n
C
n
d
%
999911658
q^{\sum_{d|n} C_{n}^{d}}\ \equiv\ q^{\sum_{d|n} C_{n}^{d}\%999911658}
q∑d∣nCnd ≡ q∑d∣nCnd%999911658
因此我们的目标就是要求解这一串数的值: ∑ d ∣ n C n d % 999911658 \sum_{d|n} C_{n}^{d}\%999911658 ∑d∣nCnd%999911658
然后我们对模数
P
P
P分解质因数,得到:
999911659
=
2
∗
3
∗
4679
∗
35617
999911659\ =\ 2*3*4679*35617
999911659 = 2∗3∗4679∗35617。可以分别计算。
然后这一题就非常好玩了,我们可以暴力求
n
n
n的每一个因数,得到对于的
C
n
d
C_{n}^{d}
Cnd对四个质数取模的值,分别累加在
a
0
a_0
a0,
a
1
a_1
a1,
a
2
a_2
a2,
a
3
a_3
a3上。即
a
i
=
∑
d
∣
n
C
n
d
%
p
i
a_i\ =\ \sum_{d|n}C_{n}^{d}\%p_i
ai = ∑d∣nCnd%pi.为什么可以累加?因为最后是求和的,因此余数可以直接累加。
最后,你可以得到一个方程组:
{
x
m
o
d
2
=
a
1
x
m
o
d
3
=
a
2
x
m
o
d
4769
=
a
3
x
m
o
d
35617
=
a
4
\begin{cases} x\ mod\ 2\ =\ a_1 \\ x\ mod\ 3\ =\ a_2 \\ x\ mod\ 4769\ =\ a_3 \\ x\ mod\ 35617\ =\ a_4 \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧x mod 2 = a1x mod 3 = a2x mod 4769 = a3x mod 35617 = a4
显然这个东西直接套用中国剩余定理的结论即可;一通线性同余方程exgcd一下就好了。
然后我们就可以求解出一组 x 0 x_0 x0,则通解 x x x可以表示为: x 0 ≡ x ( m o d 999911658 ) x_0\ \equiv\ x\ (mod\ 999911658) x0 ≡ x (mod 999911658).我们只要找到最小的解即可。
为了加快求组合数的速度,我们要预处理出阶乘;乘法逆元求不求都无所谓。然后就十分愉快的得到代码了:
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL P = 999911659;
const LL p[4] = {2,3,4679,35617};
LL n,q;
LL a[4];
LL jc[100000][4];
void init(void)
{
jc[0][0] = jc[0][1] = jc[0][2] = jc[0][3] = 1;
for (LL i=1;i<=5e4;++i)
{
jc[i][0] = jc[i-1][0]*i%p[0];
jc[i][1] = jc[i-1][1]*i%p[1];
jc[i][2] = jc[i-1][2]*i%p[2];
jc[i][3] = jc[i-1][3]*i%p[3];
}
return;
}
LL power(LL a,LL b,LL c) {
LL res = 1;
while (b>0)
{
if (b%2 == 1) res = res*a%c;
a = a*a%c;
b /= 2;
}
return res;
}
LL C(LL n,LL m,LL k)
{
if (n == m) return 1;
if (n < m) return 0;
if (n<p[k] && m<p[k])
{
LL sum1 = jc[n][k];
LL sum2 = jc[n-m][k]*jc[m][k]%p[k];
return sum1*power(sum2,p[k]-2,p[k])%p[k];
}
return C(n/p[k],m/p[k],k)*C(n%p[k],m%p[k],k)%p[k];
}
void work(void)
{
for (LL i=0;i<4;++i)
{
for (LL j=1;j*j<=n;++j)
if (n%j == 0)
{
if (j*j != n) a[i] = (a[i]+C(n,j,i))%p[i];
a[i] = (a[i]+C(n,n/j,i))%p[i];
}
}
}
LL x,y;
LL exgcd(LL a,LL b,LL c)
{
if (b == 0)
{
x = c/a;
y = 0;
return a;
}
LL t = exgcd(b,a%b,c);
LL X = x,Y = y;
x = Y;
y = X-a/b*Y;
return t;
}
void china(void)
{
LL sum = 0;
LL mm = 999911658;
LL M[4] = {};
LL r[4] = {};
for (LL i=0;i<4;++i)
{
M[i] = mm/p[i];
exgcd(M[i],p[i],1);
r[i] = x;
sum = sum+r[i]*M[i]*a[i];
}
sum = (sum%mm+mm)%mm;
cout<<power(q,sum,P);
return;
}
int main(void)
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
init();
cin>>n>>q;
if (q == P) return puts("0"),0;
work();
china();
return 0;
}
一道数学题硬是被我写到了100行哈哈哈哈哈