原题链接:B3903 [NICA #3] 星空(Hard Version)
难度提示:本题难度较高,需具备较高的数学素养!!!
Easy Version 和 Hard Version 差别在于数据范围。
题目描述
小 R 有一个长度为 n n n 的序列 a a a,保证序列中的每个数都是 2 2 2 的整数次幂。
小 M 有一个数 x x x,她希望重新排列序列 a a a,使得不存在一个 i ∈ [ 1 , n ) i\in[1,n) i∈[1,n) 满足 a i + a i + 1 > x a_i+a_{i+1}>x ai+ai+1>x。重排的方式为:选择一个 1 ∼ n 1\sim n 1∼n 的排列 p p p,然后令新序列 a ′ a' a′ 满足 a i ′ = a p i a'_i=a_{p_i} ai′=api。 a ′ a' a′ 即为重排后的序列。
现在你想要知道有多少种重排的方式能满足小 M 的要求。两种重排方式不同当且仅当选择的排列 p p p 不同。
输入格式
第一行输入两个正整数 n , x n,x n,x,表示序列长度和小 M 想的那个数;
第二行输入 n n n 个正整数 a i a_i ai,表示序列;
输出格式
输出一行表示答案。答案对 1 0 9 + 7 10^9+7 109+7 取模。
样例 #1
样例输入 #1
6 46
4 8 8 16 32 32
样例输出 #1
144
提示
数据保证, 2 ≤ n ≤ 1 0 5 2 \leq n \leq 10^5 2≤n≤105, 1 ≤ a i ≤ 2 60 1 \leq a_i \leq 2^{60} 1≤ai≤260, 1 ≤ x < 2 63 1 \leq x < 2^{63} 1≤x<263。
思路
二进制处理
注意到序列中每个数都是
2
2
2 的整数次幂,我们可以从二进制的角度思考这道题。
我们约定高位靠左,最右边是第
0
0
0 位。考虑二进制表达下
x
x
x 的最高位的
1
1
1 是第
i
i
i 位,次高位的
1
1
1 是第
j
j
j 位。由题意知,若
∃
i
∈
[
1
,
n
]
,
a
i
>
2
i
,
\exist i \in [1,n],a_i\gt 2^i,
∃i∈[1,n],ai>2i,
则题目无解;其次,序列中等于
2
i
2^i
2i 的数不可能相邻排列;再其次,序列中等于
2
i
2^i
2i 的数与小于
2
i
2^i
2i 但大于
2
j
2^j
2j 的数也不可能相邻排列;其余数无排列限制。
根据上面的分析,容易想到用
c
1
,
c
2
,
c
3
c_1,c_2,c_3
c1,c2,c3 分别记录下序列中 等于
2
i
2^i
2i、小于
2
i
2^i
2i 但大于
2
j
2^j
2j、小于
2
j
2^j
2j 的数(下称一类数、二类数、三类数)的个数。特别地,当
x
x
x 中没有次高位的
1
1
1 时,
c
2
=
c
3
=
0
c_2=c_3=0
c2=c3=0。
排列组合
接下来,我们尝试利用排列组合计算合法排列的数量。在此之前,你需要了解一些相关的知识(大佬们可自行跳过)。
身为一个 OIer,你至少要知道加法、乘法原理和排列组合数(不懂的回家种地自行查阅),此处略去不表,这里主要介绍一个常用方法:插板法。
常见的插板题,也称不可空插板题,即将
n
n
n 个完全相同的元素顺序分成
k
k
k 组,保证每组非空,求方案数。这种问题可以简单地转化为:在
n
n
n 个完全相同的元素之间的
n
−
1
n-1
n−1 个空隙中,插入
k
−
1
k-1
k−1 个板(此处,板象征划分点),求方案数。由于板互不区分,所以显然方案数为
(
n
−
1
k
−
1
)
\binom{n-1}{k-1}
(k−1n−1)。
一个稍微复杂一点的可空插板题与常见的几乎没有区别,但是如其名地不保证每组非空,即这类插板题允许存在空组。比较常用的推导方法是添加
k
k
k 个元素,读者可以自行查阅。此处介绍一种本质上利用了多重集排列数的推导方法:实际上,由于允许空组的出现,方案数等于
n
n
n 个元素和
k
−
1
k-1
k−1 个板的全排列数量,也就是
(
n
+
k
−
1
)
!
n
!
(
k
−
1
)
!
=
(
n
+
k
−
1
k
−
1
)
.
\frac{(n+k-1)!}{n!(k-1)!}=\binom{n+k-1}{k-1}.
n!(k−1)!(n+k−1)!=(k−1n+k−1).
上式即可空插板法的公式。
我们回到正题。
尝试用插板法的思想考虑重排列序列的过程。
我们先考虑一类数,由于这
c
1
c_1
c1 个一类数互不相邻,所以可以以它们为板,将
c
3
c_3
c3 个三类数隔开即可,此即不可空插板法,方案数为
(
c
3
−
1
c
1
)
\binom{c_3-1}{c_1}
(c1c3−1)。
再考虑二类数,此时以三类数为板。二类数仅不与一类数相邻,相当于从原来的
c
3
c_3
c3 个板中减掉了
c
1
c_1
c1 个,再用这些板将二类数隔开,允许空组。此即可空插板法,方案数为
(
c
2
+
c
3
−
1
c
3
−
c
1
)
\binom{c_2+c_3-1}{c_3-c_1}
(c3−c1c2+c3−1)。
(请读者认真品味上述过程,理清为什么第一步使用不可空插板法,第二步使用可空插板法)。
到这里还没结束,稍加思索便会发现,插板法中元素是完全相同的,据题意,此题中的元素显然是相互区分的,因此,最后还要乘上三个类别的数各自的全排列数。根据乘法原理,最终结果即
c
1
!
c
2
!
c
3
!
(
c
3
−
1
c
1
)
(
c
2
+
c
3
−
1
c
3
−
c
1
)
.
c_1!c_2!c_3!\binom{c_3-1}{c_1}\binom{c_2+c_3-1}{c_3-c_1}.
c1!c2!c3!(c1c3−1)(c3−c1c2+c3−1).
组合数的计算
感觉上一步的推导很绕吗?告诉你个好消息, 这一节也挺绕的……
显然,我们需要对一个组合数取模。取模在加法、减法、乘法意义下都可以通过将两侧的运算数同时取模来缩小范围,但是组合数的计算公式出现了除法……我们自然不能对分子分母同时取模。这个时候,就引出了乘法逆元的概念:
设 m ∈ N ∗ , a ∈ Z m\in\mathbf{N^*},a\in\mathbf{Z} m∈N∗,a∈Z,且 ( a , m ) = 1 (a,m)=1 (a,m)=1,则同余方程 a x ≡ 1 ( m o d m ) ax\equiv 1\pmod{m} ax≡1(modm) 有唯一解,称这个解为 a a a 对模 m m m 的乘法逆元(数论中又叫数论倒数),记为 a − 1 ( m o d m ) a^{-1}\pmod{m} a−1(modm),不引起混淆的情况下简记为 a − 1 a^{-1} a−1。
由乘法逆元的定义,若只保证 ( a , m ) = 1 (a,m)=1 (a,m)=1,则乘法逆元可以通过扩展欧几里得算法求解线性同余方程解得;但在 OI 竞赛中,通常要求对一个质数 p p p 取模,而根据费马小定理, a p − 1 ≡ a ⋅ a p − 2 ≡ 1 ( m o d p ) a^{p-1}\equiv a\cdot a^{p-2}\equiv 1\pmod{p} ap−1≡a⋅ap−2≡1(modp),故此时, a a a 对模 p p p 的乘法逆元就是 a p − 2 ( m o d p ) a^{p-2}\pmod{p} ap−2(modp),而这个结果可以用快速幂在 O ( log 2 n ) O(\log_2{n}) O(log2n) 的时间复杂度内求得。
回到本题。题目要求我们将结果对
1
0
9
+
7
10^9+7
109+7(是一个质数,这些竞赛中常见的质数模数也要多积累) 取模。考虑组合数的计算:
(
n
m
)
=
n
!
m
!
(
n
−
m
)
!
,
\binom{n}{m}=\frac{n!}{m!(n-m)!},
(mn)=m!(n−m)!n!,
我们可以预处理出题目数据范围内的所有阶乘模
1
0
9
+
7
10^9+7
109+7 的结果,再通过快速幂求解出相应的乘法逆元。我们设
f
a
c
fac
fac 保存阶乘模
1
0
9
+
7
10^9+7
109+7的结果,
f
a
c
_
i
n
v
fac\_inv
fac_inv 保存对应的乘法逆元。对于,
f
a
c
fac
fac,我们可以很容易地得到递推公式:
f
a
c
i
≡
{
1
,
i
=
0
,
f
a
c
i
−
1
×
i
,
i
>
0
(
m
o
d
1
0
9
+
7
)
.
fac_i\equiv \begin{cases} 1, & i=0,\\ fac_{i-1}\times i, & i>0 \\ \end{cases} \pmod{10^9+7}.
faci≡{1,faci−1×i,i=0,i>0(mod109+7).
相应地,对于乘法逆元,我们也可以进行递推,而不需要逐个进行快速幂。我们假设已经求得
f
a
c
_
i
n
v
i
fac\_inv_i
fac_invi,现在想要求
f
a
c
_
i
n
v
i
−
1
fac\_inv_{i-1}
fac_invi−1。根据乘法逆元的定义:
f
a
c
_
i
n
v
i
×
i
!
≡
f
a
c
_
i
n
v
i
×
(
i
−
1
)
!
×
i
≡
1
(
m
o
d
1
0
9
+
7
)
⇒
f
a
c
_
i
n
v
i
−
1
≡
f
a
c
_
i
n
v
i
×
i
(
m
o
d
1
0
9
+
7
)
⇒
f
a
c
_
i
n
v
i
≡
{
f
a
c
max
1
0
9
+
5
,
i
=
max
,
f
a
c
i
+
1
×
(
i
+
1
)
,
i
<
max
(
m
o
d
1
0
9
+
7
)
.
\begin{align*} & fac\_inv_{i}\times i!\equiv fac\_inv_{i}\times(i-1)!\times i\equiv1\pmod{10^9+7} \\ \Rightarrow & fac\_inv_{i-1}\equiv fac\_inv_i\times i\pmod{10^9+7} \\ \Rightarrow & fac\_inv_i\equiv \begin{cases} fac_{\max}^{10^9+5}, & i=\max,\\ fac_{i+1}\times (i+1), & i<\max \\ \end{cases} \pmod{10^9+7}. \end{align*}
⇒⇒fac_invi×i!≡fac_invi×(i−1)!×i≡1(mod109+7)fac_invi−1≡fac_invi×i(mod109+7)fac_invi≡{facmax109+5,faci+1×(i+1),i=max,i<max(mod109+7).
因此,我们只需用快速幂求出
f
a
c
_
i
n
v
max
fac\_inv_{\max}
fac_invmax,再倒序递推即可。预处理的整体时间复杂度为
O
(
max
)
O(\max)
O(max)。
预处理过后,我们便可以
O
(
1
)
O(1)
O(1) 的计算出每个组合数对应的模,即
(
n
m
)
≡
f
a
c
n
×
f
a
c
_
i
n
v
m
×
f
a
c
_
i
n
v
n
−
m
(
m
o
d
1
0
9
+
7
)
.
\binom{n}{m}\equiv fac_n\times fac\_inv_{m}\times fac\_inv_{n-m}\pmod{10^9+7}.
(mn)≡facn×fac_invm×fac_invn−m(mod109+7).
代码
#include <cstdio>
#include <cmath>
using namespace std;
constexpr int MAX_N = 1e5, MOD = 1e9 + 7;
int fac[MAX_N + 10], fac_inv[MAX_N + 10];
int t, c1, c2, c3;
unsigned long long x;
int quick_power(long long a, long long b) {
a %= MOD;
b %= MOD;
int ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = static_cast<long long>(ans) * a % MOD;
a = a * a % MOD;
}
return ans;
}
void calc_fac_mod() {
fac[0] = fac_inv[0] = 1;
for (int i = 1; i <= MAX_N + 2; ++i) {
fac[i] = static_cast<long long>(fac[i - 1]) * i % MOD;
}
fac_inv[MAX_N + 2] = quick_power(fac[MAX_N + 2], MOD - 2);
for (int i = MAX_N + 1; i >= 1; --i) {
fac_inv[i] = static_cast<long long>(fac_inv[i + 1]) * (i + 1) % MOD;
}
}
int c(int n, int m) {
if (n == m) return 1;
if (n < 0 || m < 0 || n < m) return 0;
return static_cast<long long>(fac[n]) * fac_inv[m] % MOD * fac_inv[n - m] % MOD;
}
int main() {
calc_fac_mod();
scanf("%d%lld", &t, &x);
unsigned long long p2;
unsigned long long p1 = 1ull << static_cast<int>(log2(x));
if (x - p1 == 0) p2 = 0;
else p2 = 1ull << static_cast<int>(log2(x - p1));
unsigned long long a;
for (int i = 1; i <= t; ++i) {
scanf("%lld", &a);
if (a > p1) {
printf("0\n");
return 0;
}
if (a == p1) ++c1;
else if (a > p2) ++c2;
else ++c3;
}
int ans = static_cast<long long>(fac[c1]) * fac[c2] % MOD * fac[c3] % MOD * c(c3 + 1, c1) % MOD * c(c2 + c3 - c1, c3 - c1) % MOD;
printf("%d\n", ans);
return 0;
}