题目大意:
题目链接:https://jzoj.net/senior/#main/show/3057
有
n
n
n个人买电影一的票,
m
m
m个人买电影二的票。每次只有一个人买票,而且保证每时每刻电影一的票房都不小于电影二的票房。求买票顺序的方案数。
思路:
假设买电影一的票的人为
a
a
a,买电影儿的票的人为
b
b
b,那么买票顺序就构成了一个序列。例如
a
a
b
b
b
,
a
a
b
a
b
b
b
a
b
,
a
b
a
b
a
aabbb,aababbbab,ababa
aabbb,aababbbab,ababa。那么一个序列中有
n
n
n个
a
a
a和
m
m
m个
b
b
b就相当于在一个长度为
n
+
m
n+m
n+m的字符串里,选择其中
n
n
n个成为
a
a
a,剩下的都是
b
b
b。
那么总的方案数就转化了在
n
+
m
n+m
n+m个数中选择
n
n
n个数的方案数。自然就是
C
n
+
m
n
C^n_{n+m}
Cn+mn了。
但是其中还要去除其中某个时刻电影二的票房大于电影一的票房的情况。也就是说,对于一个
a
b
ab
ab串,当某一个时刻
p
p
p(也就是该串的前
p
p
p个字符)的时候,
s
[
1
∼
p
]
s[1\sim p]
s[1∼p]中
b
b
b的个数大于
a
a
a的个数,那么该情况就是要舍去的。
如果该串有多个时刻
p
p
p满足上述条件,那么取最前面的为
p
p
p。
容易得出,此时
b
b
b的个数一定比
a
a
a的个数多一。那么如果把
s
[
1
∼
p
]
s[1\sim p]
s[1∼p]中的
a
a
a和
b
b
b取反(
a
a
a变
b
b
b,
b
b
b变
a
a
a),那么由于
b
b
b的个数比
a
a
a的个数多一,所以去反后就会出现
n
+
1
n+1
n+1个
a
a
a和
m
−
1
m-1
m−1个
b
b
b。
那么只需找到有多少个
a
b
ab
ab串含
n
+
1
n+1
n+1个
a
a
a和
m
−
1
m-1
m−1个
b
b
b就可以了。因为取反后就会有
n
n
n个
a
a
a和
m
m
m个
b
b
b。
那么求有多少个需要排除的答案就转换为在
(
n
+
1
)
+
(
m
−
1
)
=
n
+
m
(n+1)+(m-1)=n+m
(n+1)+(m−1)=n+m个数字中选择
n
+
1
n+1
n+1个的方案数,即
C
n
+
m
n
+
1
C^{n+1}_{n+m}
Cn+mn+1。
那么最终答案就是总方案数
−
-
−需排除方案数,即
C
n
+
m
n
−
C
n
+
1
n
−
m
C^{n}_{n+m}-C{n+1}_{n-m}
Cn+mn−Cn+1n−m
利用通项公式得
(
n
+
m
)
!
n
!
[
(
n
+
m
)
−
n
]
!
−
(
n
+
m
)
!
(
n
+
1
)
!
[
(
n
+
1
)
+
(
n
−
m
)
]
!
\frac{(n+m)!}{n![(n+m)-n]!}-\frac{(n+m)!}{(n+1)![(n+1)+(n-m)]!}
n![(n+m)−n]!(n+m)!−(n+1)![(n+1)+(n−m)]!(n+m)!
简化得
(
n
+
m
)
!
n
!
m
!
−
(
n
+
m
)
!
(
n
+
1
)
!
(
m
−
1
)
!
\frac{(n+m)!}{n!m!}-\frac{(n+m)!}{(n+1)!(m-1)!}
n!m!(n+m)!−(n+1)!(m−1)!(n+m)!
将分母中的
n
!
n!
n!和
(
n
+
1
)
!
(n+1)!
(n+1)!约分得
(
n
+
1
)
(
n
+
2
)
.
.
.
(
n
+
m
)
m
!
−
(
n
+
2
)
(
n
+
3
)
.
.
.
(
n
+
m
)
(
m
−
1
)
!
\frac{(n+1)(n+2)...(n+m)}{m!}-\frac{(n+2)(n+3)...(n+m)}{(m-1)!}
m!(n+1)(n+2)...(n+m)−(m−1)!(n+2)(n+3)...(n+m)
令
T
=
(
n
+
2
)
(
n
+
3
)
.
.
.
(
n
+
m
)
T=(n+2)(n+3)...(n+m)
T=(n+2)(n+3)...(n+m)得
T
(
n
+
1
)
m
!
−
T
(
m
−
1
)
!
\frac{T(n+1)}{m!}-\frac{T}{(m-1)!}
m!T(n+1)−(m−1)!T
将第二个分式上下同时乘
m
m
m得
T
(
n
+
1
)
m
!
−
T
m
m
!
\frac{T(n+1)}{m!}-\frac{Tm}{m!}
m!T(n+1)−m!Tm
即
T
(
n
+
1
−
m
)
m
!
\frac{T(n+1-m)}{m!}
m!T(n+1−m)
我们要求的就是这个鬼东西了。
那么就可以暴力搞了。不需要像题解中分解质因数什么的。
注意的是,该题需要压位高精,压
8
8
8位后分子
T
(
n
+
1
−
m
)
T(n+1-m)
T(n+1−m)还是可能会很大,需要长度
3000
3000
3000的数组。而且在运算过程中,分子最高会达到
1
0
8
×
1
0
8
10^8\times 10^8
108×108,还是必须开
l
o
n
g
l
o
n
g
long\ long
long long。
求出分子后,可以依次除以
1
,
2
,
3...
m
−
1
,
m
1,2,3...m-1,m
1,2,3...m−1,m,依旧可以达到直接除以
m
!
m!
m!的效果。
时间复杂度
O
(
m
×
M
A
X
N
)
O(m\times MAXN)
O(m×MAXN),其中
M
A
X
N
MAXN
MAXN表示数组长度(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;
}