题解:B3903 [NICA #3] 星空(Hard Version)

原题链接: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 1n 的排列 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 2n105 1 ≤ a i ≤ 2 60 1 \leq a_i \leq 2^{60} 1ai260 1 ≤ x < 2 63 1 \leq x < 2^{63} 1x<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 n1 个空隙中,插入 k − 1 k-1 k1(此处,板象征划分点),求方案数。由于板互不区分,所以显然方案数为 ( n − 1 k − 1 ) \binom{n-1}{k-1} (k1n1)
一个稍微复杂一点的可空插板题与常见的几乎没有区别,但是如其名地不保证每组非空,即这类插板题允许存在空组。比较常用的推导方法是添加 k k k 个元素,读者可以自行查阅。此处介绍一种本质上利用了多重集排列数的推导方法:实际上,由于允许空组的出现,方案数等于 n n n 个元素和 k − 1 k-1 k1 个板的全排列数量,也就是
( 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!(k1)!(n+k1)!=(k1n+k1).
上式即可空插板法的公式。


我们回到正题。

尝试用插板法的思想考虑重排列序列的过程。
我们先考虑一类数,由于这 c 1 c_1 c1 个一类数互不相邻,所以可以以它们为板,将 c 3 c_3 c3 个三类数隔开即可,此即不可空插板法,方案数为 ( c 3 − 1 c 1 ) \binom{c_3-1}{c_1} (c1c31)
再考虑二类数,此时以三类数为板。二类数仅不与一类数相邻,相当于从原来的 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} (c3c1c2+c31)

(请读者认真品味上述过程,理清为什么第一步使用不可空插板法,第二步使用可空插板法)。

到这里还没结束,稍加思索便会发现,插板法中元素是完全相同的,据题意,此题中的元素显然是相互区分的,因此,最后还要乘上三个类别的数各自的全排列数。根据乘法原理,最终结果即
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!(c1c31)(c3c1c2+c31).

组合数的计算

感觉上一步的推导很绕吗?告诉你个好消息, 这一节也挺绕的……
显然,我们需要对一个组合数取模。取模在加法、减法、乘法意义下都可以通过将两侧的运算数同时取模来缩小范围,但是组合数的计算公式出现了除法……我们自然不能对分子分母同时取模。这个时候,就引出了乘法逆元的概念:

m ∈ N ∗ , a ∈ Z m\in\mathbf{N^*},a\in\mathbf{Z} mN,aZ,且 ( a , m ) = 1 (a,m)=1 (a,m)=1,则同余方程 a x ≡ 1 ( m o d m ) ax\equiv 1\pmod{m} ax1(modm) 有唯一解,称这个解为 a a a 对模 m m m 的乘法逆元(数论中又叫数论倒数),记为 a − 1 ( m o d m ) a^{-1}\pmod{m} a1(modm),不引起混淆的情况下简记为 a − 1 a^{-1} a1

由乘法逆元的定义,若只保证 ( 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} ap1aap21(modp),故此时, a a a 对模 p p p 的乘法逆元就是 a p − 2 ( m o d p ) a^{p-2}\pmod{p} ap2(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!(nm)!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,faci1×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_invi1。根据乘法逆元的定义:
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×(i1)!×i1(mod109+7)fac_invi1fac_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_invnm(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;
}
  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值