BZOJ2186: [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
HINT
Source
数论

假如一个数 x 和另一个数 y 互质,事实上我们就可以得到 x+y,x+2y,... 都是与 y 互质的(应该很好理解吧)。运用这个性质,我们只要求出 M! 内与 M! 互质的数的个数,然后再乘以 N!M! 即可(易知有这么多个)。
所以对于一组 N,M ,答案应为 φ(M!)N!M!
再来看 φ(M!) M!=123...M ,这个家伙虽然很大,但要知道它也是可以标准分解的,即 M!=pa11pa22pa33... ,其中 p1,p2,p3... 都是素数,由于欧拉函数的公式就可以知道 φ(M!)=M!(p11)/p1(p21)/p2... ,而 M! 覆盖面这么广(2到M都被它覆盖到了!),我们就可以知道 p1,p2,p3... 其实就是所有小于等于 M 的质数!
于是至此最终的答案就为 φ(M!)N!M!=M!(p11)/p1(p21)/p2...N!M!=N!(p11)/p1(p21)/p2...
又注意到内存这么大,显然就是要预处理了,有以下4项:
1、所有素数(线筛即可)。
2、所有数的逆元(不能只处理那些素数的逆元)。
这里可以采取基于 ai+b=p 的求法,也就是下面这个程序段:

void workinv()
{
    inv[1]=1;
    for (int i=2;i<=N;++i)
    {
        inv[i]=(long long)(p-p/i)*inv[p%i]%p;
        if (inv[i]<0) inv[i]+=p;
    }
}

自己好好写写,应该不难理解。
3、阶乘。
4、一个 ans 数组, ans[i] 存储所有小于等于 i 的素数的 (p1)/p 形式的乘积(注意要用逆元)。预处理很简单,如果i是素数,那么 ans[i]=ans[i1]((i1)/i) ,否则 ans[i]=ans[i1]

另外有一个小优化:对于逆元、阶乘和ans数组,当大于p(即题目中的R)的时候就都为0了(因为要取模),所以预处理可以只处理到 min(N,p)
由于取模时总是出现负数,一气之下决定牺牲一部分时间,强行加回来。

#include
#include
#define isnum(t) ((t>=48)and(t<=57))
typedef long long ll;
inline void in(int &x)
{
    char t=getchar();int f=1;x=0;
    while(!isnum(t)){if(t=='-')f=-1;t=getchar();}
    while(isnum(t)){x=x*10+t-48;t=getchar();}
    x*=f;
}
using namespace std;
const int N=10000000;
int n,m,t,p,top;
int prime[N+10],inv[N+10],fac[N+10],ans[N+10];
bool notprime[N];
void workprime()
{
    for (int i=2;i<=N;++i)
    {
        if (!notprime[i]) prime[++top]=i;
        for (int j=1;(j<=top)and(i*prime[j]1;
            if (i%prime[j]==0) break;
        }
    }
}
void workinv()
{
    inv[1]=1;
    for (int i=2;i<=min(N,p);++i)
    {
        inv[i]=(long long)(p-p/i)*inv[p%i]%p;
        if (inv[i]<0) inv[i]+=p;
    }
}
void workfac()
{
    fac[1]=1;
    for (int i=2;i<=min(N,p);++i)
    {
        fac[i]=(long long)fac[i-1]*i%p;
        if (fac[i]<0) fac[i]+=p;
    }
}
void workans()
{
    ans[1]=1;
    for (int i=2;i<=min(N,p);++i)
    if (!notprime[i])
    {
        ans[i]=(long long)ans[i-1]*(i-1)%p;
        ans[i]=(long long)ans[i]*inv[i]%p;
        if (ans[i]<0) ans[i]+=p;
    }
    else ans[i]=ans[i-1];
}
int main()
{
    in(t),in(p);
    workprime();
    workinv();
    workfac();
    workans();
    while(t--)
    {
        int n,m;in(n),in(m);
        printf("%lld\n",(long long)fac[n]*ans[m]%p);
    }
    return 0;
}

你可能感兴趣的:(刷题)