题解:ARC230E Minister
极其困难题。
考察一个顺序 \(p_1,\cdots,p_n\),不难想到,按照这个顺序摸球,最终得分不为 \(0\) 的充要条件为:对于每个 \(2\leq i\leq n\),\(p_i+\min\limits_{1\leq j<i}p_j<m\)。必要性显然,充分性保留编号最小的球即可。
再来考虑最终得分,一个 \(p_i\) 能被保留要求对于每个 \(i<j\leq n\),都有 \(p_i+p_j<m\),因此最终得分的上界就是满足条件的 \(p_i\) 的最大值。这个上界是容易取到的,遇到满足条件的 \(p_i\) 之前保留编号最小的球;否则保留编号最大的满足条件的 \(p_i\) 即可。
现在只需要对所有合法排列求最大得分之和。
自然地去想插入 DP,但是我们发现插入一个数的时候并不太容易维护排列的合法性。
设 \(lim=\left\lfloor\dfrac{m-1}{2}\right\rfloor\),把 \(1,\cdots,n\) 按照 \(\leq lim\) 或者 \(>lim\) 分成小数和大数,那么实际上阻碍我们的情况就是:在大数前面插入一个小数,使得这个大数从不合法变得合法。
考虑非常神仙的一步:我们尝试钦定一个更好的插入顺序,使得插入一个小数不会让大数变得合法。
不妨考虑从大到小插入大数,插入一个大数 \(x\) 之前,将所有尚未被加入的、满足 \(x+y<m\) 的小数 \(y\) 插入。更具体地,设 \(k=m-1-n\),初始时插入 \(1,\cdots,k\) 和 \(n\),然后交替插入 \(k+1,m-1-(k+1),k+2,m-1-(k+2),\cdots\)。这样新插入的小数不会让旧的大数变得合法,新插入的大数也不会因为旧的数变得合法。
分讨 \(p_1\) 是不是大数。若 \(p_1\) 是小数,则 \(p\) 合法等价于每个大数 \(x\) 前面都有满足 \(x+y<m\) 的小数 \(y\);若 \(p_1\) 是大数,交换 \(p_1,p_2\) 显然不影响合法性和最大分数。因此我们可以直接对 \(p_1\) 是小数的排列计数,统计答案时给 \(p_2\) 是大数的排列的权值乘 \(2\) 即可。这样处理是为了统一大数的判定条件。
考虑插入一个数 \(x\) 时最大得分的变化。考察最后一个大数的位置 \(p\),若 \(x\) 插到了 \(p\) 之前,那么 \(x\) 不满足条件,最大得分不变;若插到了 \(p\) 之后,\(x\) 显然满足条件,而原本满足条件的大数变得不满足条件,原本满足条件的小数都 \(<x\),因此 \(x\) 直接成为了新的最大得分。
设计 DP:令 \(f_{i,0/1}\) 表示当前最后一个大数是 \(p_i\),\(p_2\) 是否为大数,此时合法排列的方案数,\(g_{i,0/1}\) 表示合法排列的得分和。
初始时插入 \(1,\cdots,k\) 和 \(n\),枚举 \(n\) 的位置 \(2\leq i\leq k+1\),则 \(f_{i,[i=2]}=k!,g_{i,[i=2]}=nk!\)。
设当前已经处理了 \(sz\) 个数。
插入一个小数 \(x\) 时,转移为:
三条转移分别表示:插入位置 \(1,2\)、插入位置 \(3,\cdots,i\)、插到位置 \(i\) 之后。
插入一个大数 \(x\) 时,转移为:
三条转移分别表示:插入位置 \(2\)、插入位置 \(3,\cdots,i\)、插到位置 \(i\) 之后,其中 \(i<j\leq sz+1\)。第三条转移容易前缀和优化。
时间复杂度 \(\mathcal{O}(n^2)\)。
主要代码
int n, m;
mint fac[MAXN], ifac[MAXN];
struct DP {
mint cnt, sum;
DP &operator+=(const DP &rhs) {
cnt += rhs.cnt;
sum += rhs.sum;
return *this;
}
friend DP operator+(DP lhs, const DP &rhs) {
return lhs += rhs;
}
DP operator*(mint k) {
return {cnt * k, sum * k};
}
} dp[MAXN][2], ndp[MAXN][2];
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i;
ifac[n] = fac[n].inv();
for (int i = n - 1; i >= 0; --i) ifac[i] = ifac[i + 1] * (i + 1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
if (m <= n + 1) {
cout << 0;
return 0;
}
init(n);
int k = m - n - 1, lim = m - 1 >> 1;
for (int i = 2; i <= k + 1; ++i) dp[i][i == 2] = {fac[k], fac[k] * n};
vector<int> ord;
for (int i = k + 1; i <= lim; ++i) {
ord.emplace_back(i);
if (m - i - 1 > lim) ord.emplace_back(m - i - 1);
}
int sz = k + 1;
for (int x : ord) {
for (int i = 2; i <= n; ++i) ndp[i][0] = ndp[i][1] = {0, 0};
array<mint, 2> pre = {};
for (int i = 2; i <= sz + 1; ++i) {
for (int j = 0; j < 2; ++j) {
auto [c, s] = dp[i][j];
if (x <= lim) {
ndp[i + 1][0] += dp[i][j] * 2;
ndp[i + 1][j] += dp[i][j] * (i - 2);
ndp[i][j] += DP{c, c * x} * (sz + 1 - i);
} else {
ndp[i + 1][1] += dp[i][j];
ndp[i + 1][j] += dp[i][j] * (i - 2);
ndp[i][j] += DP{pre[j], pre[j] * x};
pre[j] += c;
}
}
}
++sz;
for (int i = 2; i <= sz; ++i) {
dp[i][0] = ndp[i][0];
dp[i][1] = ndp[i][1];
}
}
mint ans = 0;
for (int i = 2; i <= n; ++i) ans += dp[i][0].sum + dp[i][1].sum * 2;
cout << ans * ifac[n];
return 0;
}

浙公网安备 33010602011771号