Codeforces Round #449 (Div. 1) D. Nephren Runs a Cinema 卡特兰数,逆元,欧拉函数,

D. Nephren Runs a Cinema
time limit per test2.5 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard output
Lakhesh loves to make movies, so Nephren helps her run a cinema. We may call it No. 68 Cinema.

However, one day, the No. 68 Cinema runs out of changes (they don’t have 50-yuan notes currently), but Nephren still wants to start their business. (Assume that yuan is a kind of currency in Regulu Ere.)

There are three types of customers: some of them bring exactly a 50-yuan note; some of them bring a 100-yuan note and Nephren needs to give a 50-yuan note back to him/her; some of them bring VIP cards so that they don’t need to pay for the ticket.

Now n customers are waiting outside in queue. Nephren wants to know how many possible queues are there that they are able to run smoothly (i.e. every customer can receive his/her change), and that the number of 50-yuan notes they have after selling tickets to all these customers is between l and r, inclusive. Two queues are considered different if there exists a customer whose type is different in two queues. As the number can be large, please output the answer modulo p.

Input
One line containing four integers n (1 ≤ n ≤ 105), p (1 ≤ p ≤ 2·109), l and r (0 ≤ l ≤ r ≤ n).

Output
One line indicating the answer modulo p.

Examples
input
4 97 2 3
output
13
input
4 100 0 4
output
35
题意:有三种人,拿50块的,拿100块的,不用付钱的,刚开始,售票点是没有钱的,票的售价是50,问这三种人有多少种排列使得每个人都能买票并且最后售票中站还剩50块的数量在l,r之间有多少种,
做法:需要用到的知识有卡特兰数,卡特兰数的拓展,欧拉函数,逆元。
具体做法是枚举vip的人数,然后计算对于每一种vip的人数,可以有多少种排列,这个可以用卡特兰数的原理来解释。
假设50为1,100位-1,最开始的位置在(0,0),最后走到(n,x),x即剩余的50块的人,那么一共有c(n,(n-x)/2)种选择方法,这里面有一些是不合法的,因为会低于-1,但是对于每一个达到-1的点,把前面的路径都对称(-1变成1,1变成-1)那么可以发现所有的不合法的路径都有且只有一个对应的,所以不和法的路径总数是c(n,(n-x-1)/2)种,所以如果剩余x个1,那么结果就是c(n,(n-x)/2)-c(n,(n-x-1)/2),这里其实n的奇偶和x的奇偶有一定的影响,找到这个公式花了我蛮多时间的,所以,如果剩下的1在l到r之间的排列方式有c(n,(n-l)/2)-c(n,(n-min(r,n)-1)/2)种。
然后是处理模数,如果模数不是质数,是不太好处理的,这里学到了一个很好的处理方式,首先要知道有一个欧拉函数Ψ(n),如果a,p互质,那么a对于p的逆元就是qpow(a,Ψ(p)-1),然后回到这题,因为要计算组合数,所以要预处理n!和inv[n!],对于x!,它含有的所有的p的质因子都提取出来,ff[x]就代表去除了p的质因子的x!,此时ff[x]和p是互质的,所以可以放心大胆的计算逆元,然后再把p含有的每个质因子再乘回来,然后就求出了排列组合对于非质数的取模。

#include
#define ll long long
using namespace std;

const int N = 1e5+100;
vector<int> v;
vector<int> vp;
int a[N][20],f[N],invf[N];
int n,p,l,r;
bool vis[N];

int qpow(int x,int b){
    ll sum = 1;
    ll now = x;
    while(b){
        if(b&1) sum = sum*now%p;
        now = now*now%p;
        b >>= 1;
    }
    return sum;
}

void init(){
    vis[1] = true;
    for(int i = 1;i < N;i ++){
        if(vis[i] == false) v.push_back(i);
        for(int j = 0;j < v.size()&&i*v[j]true;
            if(i%v[j] ==0) break;
        }
    }
    int t = p;
    for(int i = 0;i < v.size();i ++){
        if(t%v[i] == 0){
            vp.push_back(v[i]);
            while(t%v[i]==0)t/=v[i];
        }
    }

    if(t!=1) vp.push_back(t);
    int phi = p;
    for(int i = 0;i < vp.size();i ++) phi = phi/vp[i]*(vp[i]-1);
    f[0] = invf[0] = 1;
    for(int i = 1;i <= n;i ++){
        int x = i;
        for(int j = 0;j < vp.size();j ++) {
            a[i][j]=a[i-1][j];
            while(x%vp[j] == 0) a[i][j]++,x/=vp[j];
        }
        f[i] = 1LL*f[i-1]*x%p;
        invf[i]= qpow(f[i],phi-1);
    }
}
int calc(int n,int x){
    if(x < 0) return 0;
    int ret = 1LL*f[n]*invf[x]%p*invf[n-x]%p;

    for(int i = 0;i < vp.size();i ++){
        ret = 1LL*ret*qpow(vp[i],a[n][i]-a[x][i]-a[n-x][i])%p;
    }
    return ret;
}


int main(){
    scanf("%d %d %d %d",&n,&p,&l,&r);
    init();
    int ans = 0;
    for(int i = 0;i <= n-l;i ++){
        int nn = n-i;
        ans = (ans + 1LL*(calc(nn,nn-l>>1)-calc(nn,nn-min(n,r)-1>>1))*calc(n,i))%p;
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(算法理解)