【JZOJ3057】【NOIP2012模拟10.26】电影票【数论】
题目大意:
题目链接:https://jzoj.net/senior/#main/show/3057
有个人买电影一的票,个人买电影二的票。每次只有一个人买票,而且保证每时每刻电影一的票房都不小于电影二的票房。求买票顺序的方案数。
思路:
假设买电影一的票的人为,买电影儿的票的人为,那么买票顺序就构成了一个序列。例如。那么一个序列中有个和个就相当于在一个长度为的字符串里,选择其中个成为,剩下的都是。
那么总的方案数就转化了在个数中选择个数的方案数。自然就是了。
但是其中还要去除其中某个时刻电影二的票房大于电影一的票房的情况。也就是说,对于一个串,当某一个时刻(也就是该串的前个字符)的时候,中的个数大于的个数,那么该情况就是要舍去的。
如果该串有多个时刻满足上述条件,那么取最前面的为。
容易得出,此时的个数一定比的个数多一。那么如果把中的和取反(变,变),那么由于的个数比的个数多一,所以去反后就会出现个和个。
那么只需找到有多少个串含个和个就可以了。因为取反后就会有个和个。
那么求有多少个需要排除的答案就转换为在个数字中选择个的方案数,即。
那么最终答案就是总方案数需排除方案数,即
利用通项公式得
简化得
将分母中的和约分得
令得
将第二个分式上下同时乘得
即
我们要求的就是这个鬼东西了。
那么就可以暴力搞了。不需要像题解中分解质因数什么的。
注意的是,该题需要压位高精,压位后分子还是可能会很大,需要长度的数组。而且在运算过程中,分子最高会达到,还是必须开。
求出分子后,可以依次除以,依旧可以达到直接除以的效果。
时间复杂度,其中表示数组长度(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;
}