D. Excellent Arrays 解题思路
核心问题分析
题意:称数组 是 good 的,如果对每个 都有 。定义
称数组是 excellent 的,如果它 good、每个 、且 取到所有 good 数组中的最大值。给定 ,求 excellent 数组的个数,对 取模。
,,。
题意:称数组 是 good 的,如果对每个 都有 。定义
称数组是 excellent 的,如果它 good、每个 、且 取到所有 good 数组中的最大值。给定 ,求 excellent 数组的个数,对 取模。
,,。
题目里 和 都在跟下标较劲,那就干脆做一步换元:
两个条件立刻变得清爽无比:
于是 就是"数组 里有多少对数互为相反数"。
现在问题是:所有 非零,怎么让互为相反数的数对最多?
假设我们用了若干组数值 ,其中 这一组里有 个正的、 个负的,那么
而 。因为 这种乘积在"把资源集中到一组"时最大(),所以只用一个绝对值 是最优的:所有 。
设有 个 、 个 ,则 ,在 或 时取最大。所以:
excellent 数组 存在某个 ,使得每个 都等于 或 ,且 的个数是 或 。
( 为偶数时这两个值相同,只有一种; 为奇数时是两个不同的值,都要算。)
现在加上 ,也就是 。分两种情况看:
若 :需要 ,即 。(下界 自动满足,因为 而 。)
若 :需要 ,即 。(上界同理自动满足,因为 。)
于是定义两个关键量:
因为 的最紧约束在 (), 的最紧约束在 ()。令 。
第一段:。 这时每个下标都可以自由选正负,方案数与 无关:
一共 个这样的 ,直接乘起来:
ans = (M % MOD) * temp % MOD;注意 可以到 级别,要先取模。这一步把绝大部分的 一口气算完了——这是本题能过的关键。
第二段:。 此时开始有下标被"锁死":
(都要跟 和 取 夹一下。)
如果 ,说明有下标同时被两边锁死,彻底无解,直接停。否则剩下 个自由下标,还需要从中再挑出 个当正号:
int cnt = n - c1 - c2;
if (n % 2 == 0) cur = get_C(cnt, n / 2 - c1);
else cur = (get_C(cnt, n / 2 - c1) + get_C(cnt, n / 2 + 1 - c1)) % MOD;这是最容易担心的地方: 会不会一直加到 ?
不会。从 开始, 和 里至少有一个(对应 的那一边)会随 严格递增,每次加 。而组合数 变成 的条件是 (下指标为负)或者 (下指标超过 )。所以最多跑
轮就会因为 cur == 0 或 c1 + c2 > n 而退出。这就是代码里那两个 break 的意义——它们不只是优化,而是复杂度正确性的保证。
阶乘和逆元预处理 一次搞定。每组数据:第一段 ,第二段 。总计
, 秒随便过。
回头看这道 的题, 这一步换元几乎是决定性的:换完之后"最大化 "立刻退化成一个初中难度的均值不等式,"所有 只能是 "这个极强的结构也就自己冒出来了。剩下的难点在于 的范围高达 ——但只要注意到"当 足够小的时候方案数根本不随 变化",就能把无穷无尽的枚举压缩成"一段乘法 + 段暴力"。这种"大部分情况同质、只有边界附近才需要精细讨论"的分段技巧,在计数题里非常常见。
// D. Excellent Arrays
#include <bits/stdc++.h>
#define lg(x) (63 - __builtin_clzll(x))
#define all(x) (x).begin(), (x).end()
#define low_bit(x) ((x) & (-x))
#define pb push_back
#define db long double
#define int long long
#define sz(x) (int)x.size()
#define endl "\n"
using namespace std;
const int MOD = 1e9 + 7;
const int MAX = 200005;
int fact[MAX], invf[MAX];
int quick_power(int a, int b) {
int res = 1;
a %= MOD;
while (b > 0) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
void init() {
fact[0] = 1;
invf[0] = 1;
for (int i = 1; i < MAX; i++) fact[i] = fact[i - 1] * i % MOD;
invf[MAX - 1] = quick_power(fact[MAX - 1], MOD - 2);
for (int i = MAX - 2; i >= 1; i--) invf[i] = invf[i + 1] * (i + 1) % MOD;
}
int get_C(int n, int m) {
if (m < 0 || m > n || n < 0) return 0;
return fact[n] * invf[m] % MOD * invf[n - m] % MOD;
}
void solve() {
int n, l, r;
cin >> n >> l >> r;
int L = 1 - l; // 所有下标都能取 -k 的上限
int R = r - n; // 所有下标都能取 +k 的上限
int M = min(L, R);
int ans = 0, temp = 0;
if (n % 2 == 0) {
temp = get_C(n, n / 2);
} else {
temp = (get_C(n, n / 2) + get_C(n, n / 2 + 1)) % MOD;
}
// 第一段:k <= M,每个下标自由选正负
ans = (M % MOD) * temp % MOD;
// 第二段:k > M,部分下标被锁死
for (int k = M + 1; ; k++) {
int c1 = max(0LL, min(n, k - L)); // 被迫取 +k
int c2 = max(0LL, min(n, k - R)); // 被迫取 -k
if (c1 + c2 > n) break;
int cnt = n - c1 - c2;
int cur = 0;
if (n % 2 == 0) {
cur = get_C(cnt, n / 2 - c1);
} else {
cur = (get_C(cnt, n / 2 - c1) + get_C(cnt, n / 2 + 1 - c1)) % MOD;
}
if (cur == 0) break;
ans = (ans + cur) % MOD;
}
cout << ans << endl;
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
init();
int t = 1;
cin >> t;
while (t--) {
solve();
}
}