Median Equals X
Median Equals X
范德蒙卷积,Hockey-Stick恒等式,计数问题
找到所有的a[i] = x, a[i]+a[j] = 2x
通过范德蒙卷积和Hockey-Stick恒等式进行快速统计即可
1. 问题转化
给定长度为 \(n\) 的序列 \(a_1,a_2,\dots,a_n\),以及目标值 \(x\)。
一个子序列由原序列的一组下标决定,即使选出的数值完全相同,只要下标集合不同,也算不同的子序列。
设选出的子序列长度为 \(m\),排序后为 \(v_1\le v_2\le \cdots \le v_m\)。
- 若 \(m\) 为奇数,中位数为 \(v_{(m+1)/2}\);
- 若 \(m\) 为偶数,中位数为 \(\frac{v_{m/2}+v_{m/2+1}}{2}\)。
要求中位数恰好等于 \(x\)。
2. 排序后按位置计数
将原序列排序,得到有序数组
排序不会改变“不同下标集合算不同子序列”这一事实,因为每个位置仍然对应原序列中的一个下标。
因此,我们只需要在排序后的数组中,按照位置选择子序列,并统计中位数为 \(x\) 的方案数。
3. 奇数长度子序列
若子序列长度为奇数,则中位数必须是某个具体元素,因此该元素必须等于 \(x\)。
枚举中位数所在位置 \(i\),要求
如果 \(i\) 是子序列的中位数,那么:
- 在 \(i\) 左边选 \(k\) 个元素;
- 在 \(i\) 右边选 \(k\) 个元素;
- 总长度为 \(2k+1\),中位数正好是位置 \(i\) 的元素。
固定 \(i\) 时,方案数为
利用范德蒙卷积:
所以所有满足 \(a_i=x\) 的位置 \(i\),贡献为
代码中对应:
for(int i = 0; i < n; i ++) {
if(a[i] == x) ans = (ans + C(n - 1, i)) % MOD;
}
4. 偶数长度子序列
若子序列长度为偶数,则中位数是两个中间元素的平均值。
设两个中间位置分别为 \(i<j\),它们必须满足
此时:
- \(i\) 是子序列排序后的第 \(k+1\) 个元素;
- \(j\) 是子序列排序后的第 \(k+2\) 个元素;
- 在 \(i\) 左边选 \(k\) 个元素;
- 在 \(j\) 右边选 \(k\) 个元素;
- \(i\) 和 \(j\) 之间不能选任何元素,否则中间位置会改变。
因此,固定 \(i,j\) 时,方案数为
这里同样使用了范德蒙卷积。
于是问题转化为:对于每个 \(i\),找到所有满足
的位置 \(j\),并累加
5. 用 Hockey-Stick 恒等式快速求和
由于数组已经排序,所有满足 \(a_j=2x-a_i\) 的位置 \(j\) 一定形成一段连续区间。
设该区间为
可以用 lower_bound 和 upper_bound 找到。
需要计算
令
则求和变为
利用 Hockey-Stick 恒等式:
代入 \(A=n-1-r\),\(B=n-1-l\),得到
代码中对应:
term1 = C(n - l + i, i + 1);
term2 = C(n - r + i - 1, i + 1);
contrib = (term1 - term2 + MOD) % MOD;
include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
const ll N = 4e5 + 1;
vector<ll> fact(N, 0), invfact(N, 0);
ll modpow(ll a, ll b) {
ll r = 1;
while(b) {
if(b & 1) r = r * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return r;
}
void init() {
fact[0] = 1;
for(int i = 1; i < N; i ++) fact[i] = fact[i-1] * i % MOD;
invfact[N-1] = modpow(fact[N-1], MOD - 2);
for(int i = N - 2; i >= 0; i --) invfact[i] = invfact[i + 1] * (i + 1) % MOD;
}
ll C(int m, int x) {
if(x < 0 || x > m || m < 0) return 0;
return fact[m] * invfact[x] % MOD * invfact[m - x] % MOD;
}
void solve() {
int n; ll x; cin >> n >> x;
vector<ll> a(n);
for(int i = 0; i < n; i ++) cin >> a[i];
sort(a.begin(), a.end());
ll ans = 0;
for(int i = 0; i < n; i ++) {
if(a[i] == x) ans = (ans + C(n - 1, i)) % MOD;
}
for(int i = 0; i < n; i ++) {
ll k = x * 2 - a[i];
auto it = lower_bound(a.begin(), a.end(), k);
auto it2 = upper_bound(a.begin(), a.end(), k);
int l = it - a.begin(), r = it2 - a.begin();
l = max(l, i + 1); r --;
if(l <= r) {
ll term1 = C(n - l + i, i + 1);
ll term2 = C(n - r + i - 1, i + 1);
ll contrib = (term1 - term2 + MOD) % MOD;
ans = (ans + contrib) % MOD;
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
init();
int _ = 1; cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号