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

这两天怎么水这么多题解?

https://www.luogu.com.cn/article/dfnjx8m6


提供一种 \(O(1)\) 回答的方法,和目前题解区的都不一样。


如果存在一个 \(a_i \ge x\),那么它和任意别的数相加都大于 \(x\),答案为 \(0\)

如果 \(x\) 在二进制下只有一位且不存在 \(a_i \ge x\),显然任意的顺序都合法,答案为 \(n!\)

\(B\) 类数为 \(x\) 在二进制下最高一位所表示的数,\(A\) 类数为与 \(B\) 类数相加大于 \(x\) 的数,\(C\) 类数为其他数。

拿样例举例,\(46\) 的二进制表示为 \(101110\),那么二进制表示为 \(100000\) 的数,即 \(32\),为 \(B\) 类数。\(16\)\(A\) 类数,\(4,8\)\(C\) 类数。

那么限制就是 \(B\) 类数只能与 \(C\) 类数相邻。

\(A\) 类数有 \(a\) 个,\(B\) 类数有 \(b\)个,\(C\) 类数有 \(c\) 个。

假设已经放好了所有 \(C\) 类数,考虑把所有 \(B\) 类数放进去,现在有 \(c+1\) 个空可以插 \(B\) 类数,那么答案即为 \({c+1 \choose b}\)

现在考虑放 \(A\) 类数,设放完 \(B\) 类数后空的个数为 \(t\),那么 \(t=c+1-b\)。现在要在这 \(t\) 个空中放 \(a\) 个数,一个空可以不放数,这是个经典问题(如果你不会,请看文末),答案为 \({a+t-1 \choose t-1}\)

根据乘法原理,相乘即可得到答案。

因为每个数互不相同,最后答案要乘上 \(a!\times b! \times c!\)

答案为:

\[{c+1\choose b}\times {a+t-1\choose t-1}\times a!\times b! \times c! \]

#include <bits/stdc++.h>

#define mkp make_pair
#define fi first
#define se second
#define vec vector
#define eb emplace_back

using namespace std;

#define int long long

const int mod = 1e9 + 7;

signed main() {
  int n, X;
  cin >> n >> X;
  vec<int> a(n + 3);
  int Mx = 0;
  for (int i = 1; i <= n; i++)
    cin >> a[i], Mx = max(Mx, a[i]);

  if (Mx >= X) {
    cout << "0\n";
    return 0;
  }

  vec<int> fc(n * 2 + 3), ifc(n * 2 + 3);
  fc[0] = 1;
  for (int i = 1; i <= n * 2; i++)
    fc[i] = fc[i - 1] * i % mod;
  auto ksm = [](int a, int b = mod - 2) {
    int r = 1;
    while (b) {
      (b & 1) && (r = r * a % mod);
      a = a * a % mod;
      b >>= 1;
    }
    return r;
  };
  ifc[n * 2] = ksm(fc[n * 2]);
  for (int i = n * 2 - 1; i >= 0; i--)
    ifc[i] = ifc[i + 1] * (i + 1) % mod;

  auto Cc = [&](int n, int m) -> int {
    if (n < 0 || m < 0 || n < m) return 0;
    return fc[n] * ifc[m] % mod * ifc[n - m] % mod;
  };

  if (__builtin_popcountll(X) == 1) {
    // 计算二进制下 1 的个数
    cout << fc[n] << '\n';
    return 0;
  }

  auto tmp = X, ls = 0ll;
  while (tmp - (tmp & -tmp))
    ls = tmp, tmp -= tmp & -tmp;

  ls -= tmp;
  
  int A = 0, B = 0, C = 0;
  for (int i = 1; i <= n; i++)
    B += (a[i] == tmp), A += (tmp > a[i] && a[i] > ls);
  C = n - A - B;

  int t = C + 1 - B;
  int ans = Cc(C + 1, B) * (A ? Cc(A + t - 1, t - 1) : 1) % mod;
  
  ans *= fc[C] * fc[B] % mod * fc[A] % mod;
  ans %= mod;
  cout << ans << '\n';
}


\(m\) 个小球,要放到 \(n\) 个盒子中,盒子可以为空,求方案数。

设第 \(i\) 个盒子中有 \(x_i\) 个球,有 \(\sum\limits_{i=1}^n x_i=m\),其中 \(x_i\ge 0\)

我们不太会做 \(x_i\ge 0\),但我们会做 \(x_i\ge 1\)。把上面变一下,有 \(\sum\limits_{i=1}^n(x_i+1)=m+n\),其中 \(x_i+1\ge 1\)。相当于一共有 \(n+m\) 个球放到 \(n\) 个盒子中,盒子不能为空的方案数。\(n+m\) 个球两两之间有 \(n+m-1\) 个空,从中选 \(n-1\) 个空可以把它们分成 \(n\) 份,相当于放到 \(n\) 个盒子中。

得到答案:

\[{n+m-1\choose n-1} \]

posted @ 2024-11-15 20:46  ccxswl  阅读(20)  评论(2编辑  收藏  举报