BZOJ 2186: [Sdoi2008]沙拉公主的困惑

2186: [Sdoi2008]沙拉公主的困惑

>原题链接<

Description

  大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。

Input

第一行为两个整数T,R。R<=10^9+10,T<=10000,表示该组中测试数据数目,R为模后面T行,每行一对整数N,M,见题目描述 m<=n

Output

共T行,对于每一对N,M,输出1至N!中与M!素质的数的数量对R取模后的值

Sample Input

1 11
4 2

Sample Output

1

数据范围:
对于100%的数据,1 < = N , M < = 10000000

思路
思路:


题意即是求
$(\sum\limits_{i=1}^{N!}[gcd(i,M!)=1]mod\;R)$
的结果。但是带阶乘的我们显然是无法直接枚举的。
考虑化简式子,考虑之后发现无法化简QwQ.
我们知道有这样一个性质 即若
$(i,j)=1$ 则$(i,2\times j)$、$(i,3\times j)$也都等于1。考虑求gcd的过程中的辗转相除和更相减损法。套到此性质上即可证明。
由于$N\ge M$故$N!$一定是$M!$的倍数,所以我们只需要求出$M!$中有多少数与$M!$互质,即可发现对于每个$M!$以内的$x$,都有$\frac{N!}{M!}$个结果与之相对应。
又可知在$M!$以内和$M!$互质数的个数即为$\varphi(M!)$但是$M!$过大,我们显然是无法直接求出它的欧拉函数的。考虑其他方法。

稍加考虑,我们可以发现$M!$的质因子个数和$M$的质因子个数是一样的,考虑$O(\sqrt n)$的求欧拉函数的求法。
我们发现
$\varphi(n)=n\prod\limits_{i=1}^{n}(1-\frac {1}{p_i})$ 所以
$\varphi(M!)=M!\times \frac{\varphi(M)}{M}$

考虑之前化简过的原式,已知
$ans = \frac {\varphi(M!)*(N!)}{M!} mod\;R$我们又已知$\varphi(M!)$中有一个因子$M!$.所答案就变成了
$ans = \prod\limits_{i=1}^{M}(1-\frac {1}{p_i})\times N! \;\;mod\;R$

上式中的阶乘可以预处理出来,质数可以预处理出来,$\prod\limits_{i=1}^{M}(1-\frac {1}{p_i})$
可以预处理出来。$p_i$的逆元可以预处理出来,这些都是$O(N)$的,每次求出答案是$O(1)$的。所以总的时间复杂度是$O(N+T)$的。总的空间复杂度是$O(5*N)$的。对于本题的256MB可过,BZ可能会稍微卡一卡。

下面是代码:

// luogu-judger-enable-o2
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
const int N = 10000010;
bool np[N];
int pr[N], tot, fac[N], inv[N];
ll ans[N];
int n, m, p, t;
using namespace std;
void init() {
    int i, j;
    for(i=2;i<N;i++) {
        if(!np[i])
            pr[++tot] = i;
        for(j=1;j<=tot&&pr[j]*i<N;j++) {
            np[pr[j]*i]=1;
            if(i%pr[j]==0) break;
        }
    }
    for(fac[0] = 1, i = 1;i < N; i ++) fac[i]=1ll*fac[i-1]*i%p;
    for(inv[1] = 1, i = 2;i < N; i ++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
    for(ans[1] = 1, i = 2;i < N; i ++)
        if(!np[i]) ans[i]=ans[i-1]*(i-1)%p*inv[i%p]%p;
        else ans[i]=ans[i-1];
}
int main() {
    scanf("%lld%lld",&t,&p);
    init();
    while(t--) {
        scanf("%d%d",&n,&m);
        printf("%lld\n",fac[n]*ans[m]%p);
    }
}

 欢迎来原博客看看>原文链接<

posted @ 2018-06-13 09:04  TOBICHI_ORIGAMI  阅读(34)  评论(0编辑  收藏  举报