【JZOJ3057】【NOIP2012模拟10.26】电影票【数论】

题目大意:

题目链接:https://jzoj.net/senior/#main/show/3057
nn个人买电影一的票,mm个人买电影二的票。每次只有一个人买票,而且保证每时每刻电影一的票房都不小于电影二的票房。求买票顺序的方案数。


思路:

假设买电影一的票的人为aa,买电影儿的票的人为bb,那么买票顺序就构成了一个序列。例如aabbb,aababbbab,ababaaabbb,aababbbab,ababa。那么一个序列中有nnaammbb就相当于在一个长度为n+mn+m的字符串里,选择其中nn个成为aa,剩下的都是bb
那么总的方案数就转化了n+mn+m个数中选择nn个数的方案数。自然就是Cn+mnC^n_{n+m}了。
但是其中还要去除其中某个时刻电影二的票房大于电影一的票房的情况。也就是说,对于一个abab串,当某一个时刻pp(也就是该串的前pp个字符)的时候,s[1p]s[1sim p]bb的个数大于aa的个数,那么该情况就是要舍去的。
如果该串有多个时刻pp满足上述条件,那么取最前面的为pp
容易得出,此时bb的个数一定比aa的个数多一。那么如果把s[1p]s[1sim p]中的aabb取反(aabbbbaa),那么由于bb的个数比aa的个数多一,所以去反后就会出现n+1n+1aam1m-1bb
那么只需找到有多少个abab串含n+1n+1aam1m-1bb就可以了。因为取反后就会有nnaammbb
那么求有多少个需要排除的答案就转换为(n+1)+(m1)=n+m(n+1)+(m-1)=n+m个数字中选择n+1n+1个的方案数,即Cn+mn+1C^{n+1}_{n+m}
那么最终答案就是总方案数-需排除方案数,即
Cn+mnCn+1nmC^{n}_{n+m}-C{n+1}_{n-m}
利用通项公式得
(n+m)!n![(n+m)n]!(n+m)!(n+1)![(n+1)+(nm)]!frac{(n+m)!}{n![(n+m)-n]!}-frac{(n+m)!}{(n+1)![(n+1)+(n-m)]!}
简化得
(n+m)!n!m!(n+m)!(n+1)!(m1)!frac{(n+m)!}{n!m!}-frac{(n+m)!}{(n+1)!(m-1)!}
将分母中的n!n!(n+1)!(n+1)!约分得
(n+1)(n+2)...(n+m)m!(n+2)(n+3)...(n+m)(m1)!frac{(n+1)(n+2)...(n+m)}{m!}-frac{(n+2)(n+3)...(n+m)}{(m-1)!}
T=(n+2)(n+3)...(n+m)T=(n+2)(n+3)...(n+m)
T(n+1)m!T(m1)!frac{T(n+1)}{m!}-frac{T}{(m-1)!}
将第二个分式上下同时乘mm
T(n+1)m!Tmm!frac{T(n+1)}{m!}-frac{Tm}{m!}

T(n+1m)m!frac{T(n+1-m)}{m!}
我们要求的就是这个鬼东西了。
那么就可以暴力搞了。不需要像题解中分解质因数什么的。
注意的是,该题需要压位高精,压88位后分子T(n+1m)T(n+1-m)还是可能会很大,需要长度30003000的数组。而且在运算过程中,分子最高会达到108×10810^8 imes 10^8,还是必须开long longlong long
求出分子后,可以依次除以1,2,3...m1,m1,2,3...m-1,m,依旧可以达到直接除以mm!的效果。
时间复杂度O(m×MAXN)O(m imes MAXN),其中MAXNMAXN表示数组长度(3000),所以还是不够优秀,但是可以省去很多恶心的操作。


代码:

#include <cstdio>
using namespace std;
typedef long long ll;

const int MAXN=3000;
int n,m;
ll s,a[MAXN+1],t;

int main()
{
    scanf("%d%d",&n,&m);
    a[MAXN]=1;
    for (int i=n+2;i<=n+m;i++)  //求T
        for (int j=MAXN;j>=1;j--)
        {
            a[j]=a[j]*(ll)i+t;
            t=a[j]/100000000;
            a[j]%=100000000;
        }
    for (int i=MAXN;i>=1;i--)  //乘上(n-m+1)
    {
        a[i]=a[i]*(ll)(n-m+1)+t;
        t=a[i]/100000000;
        a[i]%=100000000;
    }
    for (int i=1;i<=m;i++)  //依次除
    {
        s=0;
        for (int j=1;j<=MAXN;j++)
        {
            s=s*100000000+a[j];
            a[j]=s/(ll)i;
            s%=(ll)i;
        }
    }
    int i=0;
    while (!a[i]) i++;
    printf("%lld",a[i]);
    i++;
    for (;i<=MAXN;i++)
    {
        if (a[i]<10) printf("0000000");
        else if (a[i]<100) printf("000000");
        else if (a[i]<1000) printf("00000");
        else if (a[i]<10000) printf("0000");
        else if (a[i]<100000) printf("000");
        else if (a[i]<1000000) printf("00");
        else if (a[i]<10000000) printf("0");
        printf("%lld",a[i]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998407.html