QOJ7402 Chiaki Chain Counting 题解

哪个撒币评的 QOJ4,这个题感觉比 sale 难一万倍,一坨大的。

首先不难发现 \(k=m-n+1\),我们首先考虑一个 \(O(Tn)\) 的做法,我们枚举主链长度 \(L\),选择 \(L\) 个标号并且排列,然后从这 \(L\) 个选择 \(k\) 个定点,再给这 \(k\) 个点排列。

我们首先考虑某个点下面挂了大小为 \(x\),环大小为 \(y\) 的子树的权值,这个方案数和 \(y\) 无关,我们现在要考虑的事情是把 \(n-L\) 划分成 \(k\) 段,每一个长度为 \(x\) 段的权值为 \(x!\),并且长度有大于等于的限制,需要求出权值积的和。

这么做有点难搞,我们考虑看作是先把环固定下来了,然后往里面插入若干元素,转化一下,现在有 \(k\) 个内部有顺序区分的盒子,有 \(S=n-L-\frac{k(k+5)}{2}\) 个不同的小球放入。这等价于先给小球排列,然后再做插板,那么总的方案数就是:

\[\frac{1}{2}\sum_L n!\binom{L}{k} k!\frac{1}{2^k} \binom{S+k-1}{k-1} \]

但是这个还不对,因为端点的位置会算重复,那么我们的贡献分为三种,左右端点都是叶子,左端点是叶子,右端点是叶子,左右端点都不是叶子。首先解决左右端点都是叶子的方案数,这个只需要改一改组合数的上指标即可。如果左右端点都不是叶子,我们需要钦定左右端点下面直接连着环。如果左端点或者右端点是叶子,那么需要给一端的下面挂一个环,总方案数是:

\[\frac{1}{2^{k+1}} k! n!\left ( \sum_L \binom{L-2}{k} \binom{S+k-1}{k-1} + \sum_L \binom{L-2}{k-2} \binom{S+k-3}{k-3} + 2 \sum_L \binom{L-1}{k-1} \binom{S+k-2}{k-2} \right ) \]

但是很不幸这个东西在 \(k \le 3\) 不对,我们把 \(k=1,2,3\) 手动算掉,这一部分比较恶心,这里略去。现在考虑 \(k>3\) 的情况。

这个式子还是错的,因为为我们会把一棵树算多次,我们在草稿纸上手玩一下,我们发现,如果主链的某个断点下挂了一个环 \(A\),并且和其在主链上最近的是环 \(B\),并且 \(B\) 和主链之间有至少一个点,那么我们 \((A,B)\) 算贡献会多一个 \(2\) 的系数。如果两端都这样就会算出来一个 \(4\) 的系数。

我们考虑算出来系数为 \(1,2,4\) 三种情况的答案,我们记 \(\text{sum1}, \text{sum2}, \text{sum200}, \text{sum201}, \text{sum211}, \text{sum3}, \text{sum30}, \text{sum31}\) 分别代表两边不挂叶子,两边挂叶子并无限制,两边挂叶子并主链上第一个和最后一个都是紧贴着主链的环,两边挂叶子并主链上有一边都是紧贴着主链的环,两边挂叶子并主链上第一个和最后一个都是不紧贴着主链的环,单边挂叶子并不限制,单边挂叶子并第一个是紧贴着主链的环,单边挂叶子并第一个不是紧贴主链的环。那么有:

\[\begin{align*} \text{sum1} &= \binom{L-2}{k} \binom{k+S-1}{k-1} ,\text{sum2} = \binom{L-2}{k-2} \binom{k+S-3}{k-3} \\ \text{sum200} &= \binom{L-2}{k-2} \times \begin{cases} 1, & S=0 \\ \binom{S+k-5}{k-5}, & S\neq 0 \end{cases} \\ \text{sum201} &= 2 \binom{L-2}{k-2} \binom{S+k-4}{k-4} - 2\,\text{sum200} \\ \text{sum211} &= \text{sum2} - (\text{sum200} + \text{sum201}) \\ \text{sum3} &= 2 \binom{L-2}{k-1} \binom{k+S-2}{k-2} ,\text{sum30} = 2 \binom{L-2}{k-1} \binom{k+S-3}{k-3} \quad\\ \text{sum31} &= \text{sum3} - \text{sum30} \\ \end{align*} \]

然后其中 \(\text{sum201},\text{sum31}\) 的方案除以 \(2\),\(\text{sum211}\) 的方案除以 \(4\),然后相加,这样我们得到了一个 \(O(Tn)\) 的做法,还无法通过。

然后接下来我们把这所有和式全部通过范德蒙德卷积缩到一起,记 \(N'=n-\frac{k(k+5)}{2}-2\),最终答案是:

\[n!k!(2^{k+1})^{-1} \left ( \binom{N'+k}{2k} + \binom{N'+k-1}{2k-2} + \binom{N'+k-2}{2k-3} \\ + 4^{-1} \binom{N'+k-2}{2k-4} + 2^{-1} \binom{N'+k-3}{2k-5} + 4^{-1} \binom{N'+k-4}{2k-6} \right ) \]

时间复杂度 \(O(T)\),可喜可贺。代码:

#include <bits/stdc++.h>
#define Debug(fmt, args...) fprintf(stderr, "(func %s, line #%d): " fmt, __func__, __LINE__, ##args), fflush(stderr)
#define debug(fmt, args...) fprintf(stderr, fmt, ##args), fflush(stderr)
#define ll long long
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define pii pair<ll, ll>
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define ld lower_bound
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define drep(i, a, b) for (int i = (a); i >= (b); i--)
#define U(i, a, b) for (int i = (a); i < (b); i++)
#define ud upper_bound
#define mem(s, k) memset(s, k, sizeof(s))
#define fi first
#define se second
#define ull unsigned long long
#define vi vector<ll>
#define fv inline void
#define fn inline static
#define sz(x) (int)(x.size())
#define bp(x) __builtin_popcount(x)
using u8 = unsigned char; using u16 = unsigned short; using u32 = unsigned; using u64 = unsigned ll;
using i16 = short; using i32 = ll; using i64 = ll; using db = long double;
using namespace std;
const i32 mod = 1e9 + 7;
template <typename T> fv chkmax(T &x, T y) { x = x < y ? y : x; }
template <typename T> fv chkmin(T &x, T y) { x = x > y ? y : x; }
fv inc(i32 &x, i32 y) { x = (x + y >= mod ? x + y - mod : x + y); }
fv dec(i32 &x, i32 y) { x = (x - y < 0 ? x - y + mod : x - y); }
fv mul(i32 &x, i32 y) { x = (u64)x * y % mod; }
fn i32 madd(i32 x, i32 y) { return (x + y >= mod ? x + y - mod : x + y); }
fn i32 msub(i32 x, i32 y) { return madd(x, mod - y); }
fn i32 qkpow(i32 a, i32 b) {
  i32 ans = 1;
  for (; b; b >>= 1) {
    if (b & 1) ans = (u64)ans * a % mod;
    a = (u64)a * a % mod;
  }
  return ans;
}
const i32 N = 1e6 + 5;
i32 n, m, k, inv4 = qkpow(4, mod - 2), ipw2[N], fac[N], ifac[N];
fv init(i32 n) {
  i32 inv2 = (mod + 1) / 2;
  rep (i, ipw2[0] = 1, n) ipw2[i] = ipw2[i - 1] * inv2 % mod;
  rep (i, fac[0] = 1, n) fac[i] = fac[i - 1] * i % mod;
  ifac[n] = qkpow(fac[n], mod - 2);
  drep (i, n - 1, 0) ifac[i] = ifac[i + 1] * (i + 1) % mod;
}
fn i32 C(i32 n, i32 m) {
  if (n == m) return 1;
  if (n < m || m < 0) return 0;
  return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
fn i32 C1(i32 n, i32 m) {
  if (n < m || m < 0) return 0;
  return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
fn i32 solve3(i32 n, i32 m, i32 k) {
  i32 ans = 0, inv2 = (mod + 1) / 2;
  m -= k * (k + 5) / 2;
  if (k == 1) {
    if (m >= 2) inc(ans, 1);
    inc(ans, C(m - 1, 2) * inv2 % mod);
  } else if (k == 2) {
    if (m >= 3) inc(ans, 1);
    if (m >= 4) inc(ans, m * (m - 3) / 2 % mod);
    inc(ans, C(m - 1, 4));
  } else if (k == 3) {
    if (m >= 5) inc(ans, (m - 4) * 3);
    inc(ans, C(m - 4, 2));
    inc(ans, C(m - 3, 3) * 6 % mod);
    inc(ans, C(m - 3, 4) * 3 % mod);
    inc(ans, C(m - 1, 6) * 3 % mod);
  }
  return ans * fac[n] % mod * ipw2[k] % mod;
}
fv sol() {
  cin >> n >> m >> k;
  if (k != m - n + 1) return cout << "0\n", void();
  if (k <= 3) return cout << solve3(n, m, k) << "\n", void();
  i32 Np = n - k * (k + 5) / 2 - 2;
  if (Np < 0) return cout << "0\n", void();
  i32 inv2 = (mod + 1) / 2;
  i32 ans = 0;
  ans = (ans + C(Np + k, 2 * k)) % mod;
  ans = (ans + C(Np + k - 1, 2 * k - 2)) % mod;
  ans = (ans + C(Np + k - 2, 2 * k - 3)) % mod;
  ans = (ans + inv4 * C(Np + k - 2, 2 * k - 4)) % mod;
  ans = (ans + inv2 * C(Np + k - 3, 2 * k - 5)) % mod;
  ans = (ans + inv4 * C(Np + k - 4, 2 * k - 6)) % mod;
  i32 res = ans * fac[n] % mod * fac[k] % mod * ipw2[k + 1] % mod;
  cout << res << "\n";
}
int main() {
  IOS; init(N - 2);
  i32 T; cin >> T;
  while (T--) sol();
}
posted @ 2026-09-28 18:47  nullpt3  阅读(17)  评论(3)    收藏  举报