Dyd's Blog

He who has a strong enough why can bear almost any how.

luoguP7519 [省选联考 2021 A/B 卷] 滚榜

too difficult for 蒟蒻

滚榜

题意

已知有 $n \le 13$ 个数 $a_i \le 10^4$ ,按 $1 \sim n$ 编号,每次选一个数给他加上 $b_i$ ,要保证加的 $b_i$ 不小于上次加的,且加完后该数排名第一(大小相同的比编号),已知 $\sum b_i = m \le 500$ ,求最后 $n$ 个数的排名情况有多少种

做题

看 $n \le 13$ 考虑状态压缩,设 d[s][i][j][k] 表示“已加的数集合为 $s$ ,上一个加的为 $i$ ,当前 $\sum b_i = j$ 且上一个 $b_i = k$ 的方案数”,有 $d[s][i][j][k] \to d[s + \{p\}][p][j + q][q]$ (满足 $a_p + q > / \ge a_i + b_i, j + q < m, q \ge k$ )时间复杂度为 $O(2^n n^2 m^2)$ 呃呃……

考虑把状态的维度剪掉,当然是剪一个 $m$ (剪 $n$ 没用啊),发现我们不必要上一个 $b_i$ ,考虑用别的前几维的信息得到 $b_i$ ,发现不是很好表示

考虑预处理,首先,我们发现数 $a_i$ 要变成最大的, $b_i$ 就要比 $b_j$ 大至少 $a_j - a_i$ (假设 $j$ 先于 $i$ ),不妨设它为 $dt[i][j]$ ,计算它要用 $O(n^2)$

其次,由于 $b$ 按出现的顺序单调不降,不妨考虑差分,设差分数组为 $c$ ,有 $b_i = \sum_{j = 1}^i c_i$ (这里假设 $b_i$ 按出现顺序排序了, $b_i$ 的 $i$ 已经不对应 $a_i$ 了),故 $\sum b_i = \sum c_i * (n - i + 1) = m$ ,所以 $c_i$ 对 $m$ 的贡献就要乘 $n - i + 1$ ,只需保证 $c_i \ge 0$ 即可,而 $c_i$ 的最小值就是 $dt[i][j]$ ( $j$ 先于 $i$ 一个)

再次定义 d[s][i][j] 为“已加的数集合为 $s$ ,上一个加的为 $i$ ,当前 $\sum b_i = j$ 的方案数”,设 $t = s - \{i\}$ ,有 $d[s][i][j] \gets d[t][k][j - dt[i][k] * (n - \mid A \mid)]$ ,时间为 $O(2^n n m)$ ,常数小可过

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 15, NN = (1 << 13) + 5, M = 500 + 5;
int n, m, nn;
LL ans;
int a[N], dt[N][N], d[NN][N][M], si[NN]; //si[s]:集合s的大小
int main()
{
scanf("%d %d", &n, &m), nn = 1 << n;
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) dt[i][j] = max(0, a[j] - a[i] + (i > j)), dt[i][0] = max(dt[i][j], dt[i][0]);
for (int i = 1; i < nn; ++i) si[i] = si[i >> 1] + (i & 1);
d[0][0][0] = 1;
for (int s = 1, t, num; s < nn; ++s)
for (int i = 1, j; i <= n; ++i) if (s >> i - 1 & 1)
for (t = s ^ 1 << i - 1, num = n - si[t], j = si[t] ? 1 : 0; j <= n; ++j) if (!j || t >> j - 1 & 1)
for (int k = dt[i][j] * num; k <= m; ++k) d[s][i][k] += d[t][j][k - dt[i][j] * num];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) ans += d[nn - 1][i][j];
printf("%lld\n", ans);
return 0;
}