dp 做题记录 1
由于笔者非常菜,所以这里面大多都是 [*2400,*3000] 的题。
我现在认识到 dp 和计数是时代主旋律还有救吗 /ll。
CF1781F Bracket Insertion(*2700)
首先,把每次加入的一对括号染成同一个颜色,这样颜色序列就组成了一个类似括号序列的东西。
一个对括号序列计数的 trick 是,区间 dp,在转移时枚举断点 \(k\) 并保证 \([l,k]\) 是一个合法的括号子序列,并且满足最外层有一层嵌套括号,形似 (...),这样转移的方案数便不会算重。
回到本题,考虑到还需要判断一个颜色序列是否变成括号序列后依旧合法,把左括号视作 \(-1\),右括号视作 \(+1\),在 dp 信息中记录前缀最大值。则括号序列合法当且仅当前缀最大值为 \(0\)。
所以,设 \(f_{l,r,x,0/1}\) 代表考虑区间 \([l,r]\),前缀最大值为 \(x\),最外层是由括号嵌套 / 括号拼接而来的概率。转移如下:
第一种转移是将多个子括号序列拼接起来,\(f_{l,k,x,1} \times (f_{k+1,r,y,0}+f_{k+1,r,y,1}) \times \displaystyle\binom{r-l+1}{r-k+1}\to f_{l,r,\max(x,y),0}\)。
第二种转移是给最外层嵌套一层括号,分两种情况:
- 加入的括号为 ():\(p \times (f_{l,r,x,0} + f_{l,r,x,1}) \to f_{l+1,r-1,\max(0,x-1),1}\)。
- 加入的括号为 )(:\((1-p) \times (f_{l,r,x,0} + f_{l,r,x,1}) \to f_{l+1,r-1,x+1,1}\)。
继续观察到,区间的性质并不是特别重要,考虑把前两维合并为一维 \(L\) 记录长度,把复杂度降到了 \(O(n^4)\)。
继续发现第一种转移的 \(x + y \to \max(x,y)\) 可以前缀和优化,记 \(z=\max(x,y)\),具体来说,转移要求的实际上是 \([z,z][1,z] \bigcup [1,z][z,z]\) 的矩阵并区域,只需要拿 \([1,z][1,z]\) 的答案减去 \([1,z-1][1,z-1]\) 的答案即可。
时空复杂度 \(O(n^3)\)。
const i32 N = 5e2 + 5, mod = 998244353;
i32 n, q, p, f[N][N][2], sum[N][N][2], fac[N], ifac[N];
fn i32 qkpow(i32 a, i32 b) {
i32 ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
}
return ans;
}
i32 C(i32 n, i32 m) {
if (n < m || m < 0) return 0;
return fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
fv init(i32 n) {
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;
}
int main() {
cin >> n >> q, init(N - 5), p = qkpow(10000, mod - 2) * q % mod;
f[0][0][0] = 1;
rep (i, 0, n) sum[0][i][0] = 1;
rep (i, 1, n) {
rep (j, 0, n) {
U (k, 1, i) {
i32 w = sum[k][j][0] % mod * (sum[i - k][j][0] + sum[i - k][j][1]) % mod;
if (j) w = (w - sum[k][j - 1][0] * (sum[i - k][j - 1][0] + sum[i - k][j - 1][1]) % mod + mod) % mod;
f[i][j][1] = (f[i][j][1] + C(i, k) * w % mod) % mod;
}
f[i][j][0] = (f[i][j][0] + p * (f[i - 1][j + 1][0] + f[i - 1][j + 1][1]) % mod) % mod;
if (!j) {
f[i][j][0] = (f[i][j][0] + p * (f[i - 1][j][0] + f[i - 1][j][1]) % mod) % mod;
sum[i][j][0] = f[i][j][0], sum[i][j][1] = f[i][j][1];
} else {
f[i][j][0] = (f[i][j][0] + (mod + 1 - p) * (f[i - 1][j - 1][0] + f[i - 1][j - 1][1]) % mod) % mod;
sum[i][j][0] = (sum[i][j - 1][0] + f[i][j][0]) % mod;
sum[i][j][1] = (sum[i][j - 1][1] + f[i][j][1]) % mod;
}
}
}
i32 ans = (f[n][0][0] + f[n][0][1]) % mod;
rep (i, 1, n) ans = ans * qkpow(i * 2 - 1, mod - 2) % mod;
cout << ans;
}
CF888F Connecting Vertices(*2500)
有了上一道题的经验,这道题应该就非常容易解决了。
首先断环成链,因为少一条边不会影响计数。
题目转化为需要连任意条线段使形成树,且任意两条线段只能相离,包含,或者相切。稍微手画一下,发现连接 \((x,y)\) 应该分为两种情况:
- 通过若干个条线段使 \(u\) 连接到 \(v\),这对应括号序列的拼接。
- 通过一条大线段直接从 \(u\) 连接到 \(v\),这对应括号序列的嵌套。
按照上面的套路区间 dp,记 \(f_{l,r,0/1}\) 为考虑区间 \([l,r]\),最外层是由一条大线段连接 / 多条小线段拼接的方案数。
转移是简单的,值得注意的是,这里嵌套的情况也应该枚举断点转移,时间 \(O(n^3)\)。
const i32 N = 1e3 + 5, mod = 1e9 + 7;
i32 n, A[N][N], f[N][N][2];
int main() {
cin >> n;
rep (i, 1, n) rep (j, 1, n) cin >> A[i][j];
rep (i, 1, n) f[i][i][0] = 1;
rep (len, 2, n) {
rep (l, 1, n - len + 1) {
i32 r = l + len - 1;
if (A[l][r]) {
U (k, l, r) f[l][r][0] = (f[l][r][0] + (f[l][k][0] + f[l][k][1]) % mod * (f[k + 1][r][0] + f[k + 1][r][1]) % mod) % mod;
}
U (k, l + 1, r) if (A[l][k]) f[l][r][1] = (f[l][r][1] + f[l][k][0] * (f[k][r][0] + f[k][r][1]) % mod) % mod;
}
}
cout << (f[1][n][0] + f[1][n][1]) % mod;
}
CF913E Logical Expression(*2400)
首先是有效查询最多只有 \(m=2^8\) 种,考虑预处理。
容易想到设 \(f_S\) 代表组合出 \(S\) 的最小长度和字典序最小字符串,但是发现对于用与和非转移时,如果左右均满足外层不存在 or 就可以直接加上 &,形如 ..&..&..&..,再考虑到进行非操作时,如果是单个变量或者外层有括号就不用再加括号。
所以定义 \(f_{S,0/1/2}\) 代表状态为 \(S\),且构成字符串中,最外层存在 or / 不存在 or / 最外层是括号,转移是简单的。
接着我们发现这个转移是有后效性的,由于转移代价恒大于 \(0\),考虑用类似 dijkstra 的方法转移,每次找到当前未确定的最小值拿出来确定,然后拿它和已经确定的值去更新其他未确定的值。
时间复杂度 \(O(k^2 m^2)\),这里 \(k=3\)。
const i32 N = 1e3 + 5, inf = 1e16, mod = 1e9 + 7, T = (1 << 8);
#define pis pair <i32, string>
pis operator + (pis a, pis b) { return mp(a.fi + b.fi, a.se + b.se); }
pis f[1 << 8][3];
i32 vis[1 << 8][3];
fv init() {
U (i, 0, T) rep (j, 0, 2) f[i][j] = mp(inf, "");
f[15][2] = mp(1, "x"), f[51][2] = mp(1, "y"), f[85][2] = mp(1, "z");
while (1) {
i32 px, py; pis mn = mp(inf, "");
U (i, 0, T) rep (j, 0, 2) if (!vis[i][j]) {
if (f[i][j] < mn) mn = f[i][j], px = i, py = j;
}
if (mn.fi == inf) break;
vis[px][py] = 1;
f[255 - px][2] = min(f[255 - px][2], (py == 2 ? mp(1, "!") + f[px][py] : mp(2, "!(") + f[px][py] + mp(1, ")")));
U (i, 0, T) rep (j, 0, 2) if (vis[i][j]) {
f[px & i][1] = min(f[px & i][1], (!py ? mp(1, "(") : mp(0, "")) + f[px][py] + (!py ? mp(1, ")") : mp(0, "")) + mp(1, "&") + (!j ? mp(1, "(") : mp(0, "")) + f[i][j] + (!j ? mp(1, ")") : mp(0, "")));
f[px & i][1] = min(f[px & i][1], (!j ? mp(1, "(") : mp(0, "")) + f[i][j] + (!j ? mp(1, ")") : mp(0, "")) + mp(1, "&") + (!py ? mp(1, "(") : mp(0, "")) + f[px][py] + (!py ? mp(1, ")") : mp(0, "")));
f[px | i][0] = min(f[px | i][0], f[px][py] + mp(1, "|") + f[i][j]);
f[px | i][0] = min(f[px | i][0], f[i][j] + mp(1, "|") + f[px][py]);
}
}
}
int main() {
init(); i32 T; cin >> T;
while (T--) {
string ch; i32 x = 0;
cin >> ch;
rep (i, 0, 7) x = (x << 1) | (ch[i] - '0');
cout << min({f[x][0], f[x][1], f[x][2]}).se << "\n";
}
}
CF1806D DSU Master(*2500)
首先,如果 \(a_1=1\),则答案等于 \(0\)。
按值从小到大插入排列,单点转移,假设当前插入 \(x\),且 \([1,x]\) 形成一个连通块,且 \(1\) 没有出边:
- \(a_x=0\),那么把 \(x\) 插入到任意一个位置都没有影响。
- \(a_x=1\),\(x\) 不能插入到最后一个,其他位置不影响。
容易列出 \(f_i\) 代表方案数,\(f_i=f_{i-1}\times (i-a_i)\)。
继续考虑算答案,设 \(k=i\) 答案为 \(g_i\),那么显然 \(g_i=g_{i-1}\times i+(1-a_i) \times f_{i-1}\)。
时空复杂度均为线性。
const i32 N = 5e5 + 5, mod = 998244353;
i32 f[N], a[N], n, ans;
fv sol() {
cin >> n, f[0] = 1, ans = 0;
U (i, 1, n) cin >> a[i], f[i] = f[i - 1] * (i - a[i]) % mod;
U (i, 1, n) {
ans = ans * i % mod + (a[i] == 0 ? f[i - 1] : 0);
cout << ans % mod << " ";
}
cout << "\n";
}
int main() {
i32 T; cin >> T;
while (T--) sol();
}
LGP7986 [USACO21DEC] HILO P
首先,我们考虑对状态进行刻画,会发现当前有效的值一定是一个包含 \(x\) 的区间。
于是,设 \(f_{i,j,0/1}\) 代表有 \(i\) 个有效的 \(\le x\) 的值,\(j\) 个 \(>x\) 的值,上一个是 LO / HI 的方案数。
但是答案要我们求的是所有情况的和,如果这么做则需要开一个辅助方案数组 \(g\),而且发现转移时需要讨论无效的数的插入情况,非常麻烦。
所以转期望,设 \(f_{i,j,0/1}\) 代表有 \(i\) 个有效的 \(\le x\) 的值,\(j\) 个 \(>x\) 的值,上一个是 LO / HI 的期望答案数。
考虑转移,这个比较显然,这里略。
写出转移式后发现可以进行前缀和优化,优化后复杂度 \(O(n^2)\)。
i32 n, x, A, B;
m64 inv[N], f[N][2], sum[N][2];
fv sol() {
cin >> n >> x, A = x, B = n - x;
rep (i, 1, n) inv[i] = m64(1) / m64(i);
rep (i, 0, A) {
rep (j, 0, B) {
f[j][0] = (sum[i][1] + sum[j][0]) * inv[i + j];
f[j][1] = (sum[i][1] + sum[j][0] + i) * inv[i + j];
sum[i][1] += f[j][1], sum[j][0] += f[j][0];
}
}
m64 ans = f[B][0];
rep (i, 1, n) ans *= i;
cout << ans << "\n";
}
int main() {
i32 T = 1;
while (T--) sol();
}
CF1425B Blue and Red of Our Faculty!(*2600)
首先,题目给定的图是由若干个公共点为 \(1\) 的环组成。
发现题目中的终止情况分两种,一种是在节点 \(1\) 终止,另外一种是在环上终止。
对两种情况分开讨论,做两次前后缀背包,查询只用查询单点值。
这个是容易的,复杂度 \(O(n^2)\)。
m64 ans, pre[N][N << 1], suf[N][N << 2];
i32 n, m, x, y, cnt, h;
vi G[N], S;
bool vis[N];
fv dfs(i32 u) {
cnt++, vis[u] = 1;
for (i32 v : G[u]) if (!vis[v]) dfs(v);
}
fn m64 qry(i32 p, i32 v) {
m64 ans = 0;
rep (i, 0, V) if (i - v <= V && i - v >= 0) ans += pre[p - 1][i] * suf[p + 1][V - (i - v)];
return ans;
}
fv ins(m64 *f, m64 *g, i32 k) {
rep (i, 0, V) if (i + k >= 0 && i + k <= V) g[i] += f[i + k];
}
int main() {
cin >> n >> m;
rep (i, 1, m) cin >> x >> y, G[x].pb(y), G[y].pb(x);
S.pb(0), vis[1] = 1;
for (i32 x : G[1]) if (!vis[x]) cnt = 1, dfs(x), S.pb(cnt);
h = S.size() - 1, pre[0][K] = suf[h + 1][K] = 1;
rep (i, 1, h) ins(pre[i - 1], pre[i], S[i]), ins(pre[i - 1], pre[i], -S[i]);
drep (i, h, 1) ins(suf[i + 1], suf[i], S[i]), ins(suf[i + 1], suf[i], -S[i]);
if (m & 1) {
rep (i, 1, h) ans += 4 * qry(i, S[i] - 1);
} else ans = pre[h][K];
mem(pre, 0), mem(suf, 0), pre[0][K] = suf[h + 1][K] = 1;
rep (i, 1, h) ins(pre[i - 1], pre[i], S[i]), ins(pre[i - 1], pre[i], -S[i]), ins(pre[i - 1], pre[i], 0);
drep (i, h, 1) ins(suf[i + 1], suf[i], S[i]), ins(suf[i + 1], suf[i], -S[i]), ins(suf[i + 1], suf[i], 0);
rep (i, 1, h) {
i32 k = S[i];
rep (j, 1, k - 1) ans += 2 * qry(i, k - 2 * j);
rep (j, 1, k - 2) ans += 2 * qry(i, k - 2 * j - 1);
}
cout << ans;
}
CF1442D Sum(*2800)
首先有结论:最多只有一行没有全选 / 全不选。
证明就是,如果有两行,那么调整到一行肯定会更优。
剩下的事情就非常简单了,需要求出 \([1,i-1] \bigcup [i+1,n]\) 的背包,这个线段树分治直接做就好了。
复杂度 \(O(n^2 \log n)\)。
const i32 N = 3e3 + 5;
i32 n, k, ans, t[N], v[N];
vi a[N];
vector <pii> s[N << 2];
fv ins(i32 p, i32 ql, i32 qr, i32 w, i32 v, i32 l = 1, i32 r = n) {
if (ql > r || qr < l || ql > qr) return ;
if (ql <= l && r <= qr) return s[p].pb({w, v});
i32 mid = (l + r) >> 1;
ins(p << 1, ql, qr, w, v, l, mid), ins(p << 1 | 1, ql, qr, w, v, mid + 1, r);
}
fv sol(i32 p, i32 l, i32 r, vi f) {
for (auto [w, v] : s[p]) {
drep (i, k, w) f[i] = max(f[i], f[i - w] + v);
}
i32 mid = (l + r) >> 1;
if (l == r) {
i32 sum = 0; ans = max(ans, f[k]);
rep (i, 1, t[l]) if (i <= k) sum += a[l][i], ans = max(ans, f[k - i] + sum);
f.clear();
return ;
} else sol(p << 1, l, mid, f), sol(p << 1 | 1, mid + 1, r, f);
f.clear();
}
int main() {
IOS;
cin >> n >> k;
rep (i, 1, n) {
cin >> t[i]; a[i].resize(t[i] + 3);
rep (j, 1, t[i]) cin >> a[i][j], v[i] += a[i][j];
ins(1, 1, i - 1, t[i], v[i]), ins(1, i + 1, n, t[i], v[i]);
}
vi f; f.resize(k + 2);
sol(1, 1, n, f);
cout << ans;
}
CF1740F Conditional Mix(*2600)
考虑对于一个可重集 \(S\),如何判断是否合法:
记 \(cnt_k\) 为 \(k\) 在 \(a\) 中的出现次数,我们对每个不同的 \(a_i\) 建立二分图的右部点,并限制其度数为 \(cnt_{a_i}\),对可重集的每个元素 \(k\) 建立左部点,并限制其度数为 \(k\)。
引入 Gale-Ryser 定理:
给定两个非负整数序列 \(p_1 \ge p_2 \ge ... \ge p_n\) 和 \(q_1 \ge q_2 \ge ... \ge q_m\) 满足 \(\displaystyle\sum p_i = \sum q_i\),存在一张二分图使得左部点度数集合为 \(\{p_i\}\),右部点度数集合为 \(\{q_i\}\) 的充要条件是 \(\forall k \in [1,n],\displaystyle\sum\limits_{i=1}^kp_i \le \sum\limits_{i=1}^m \min(q_i,k)\)。
回到本题,我们可以提前计算出来 \(L_k = \displaystyle\sum\limits_{i=1}^k \min(cnt_k, i)\),然后对 \(S\) 的方案 dp。
从大往小加入可重集中的数字,设 \(f_{i,j,k}\) 当前遍历到 \(i\),和为 \(j\),选的最小的数为 \(k\) 的方案数。
转移类似做完全背包,转移式为 \(f_{i,j,k+1} + f_{i-1,j-k,k+1} \to f_{i,j,k}\)。
由于 \(ik \le j\),所以复杂度是 \(O(n\times (\sum\dfrac{n}{i})) = O(n^2 \log n)\)。
m64 f[2][N][N], g[2][N][N];
i32 n, a[N], lim[N], cnt[N];
int main() {
cin >> n;
rep (i, 1, n) cin >> a[i], cnt[a[i]]++;
sort(cnt + 1, cnt + n + 1, greater <i32>());
rep (i, 0, n) {
f[0][0][i] = 1;
rep (j, 1, n) lim[i] += min((i32)i, cnt[j]);
}
rep (i, 1, n) {
rep (j, 0, lim[i]) f[i & 1][j][n / i + 1] = 0;
drep (k, n / i, 0) {
rep (j, 0, lim[i]) {
f[i & 1][j][k] = 0;
if (j >= k) f[i & 1][j][k] += f[(i - 1) & 1][j - k][k];
f[i & 1][j][k] += f[i & 1][j][k + 1];
}
}
}
cout << f[n & 1][n][0];
}
HDU6566 The Hanged Man
给一个复杂度稍劣的非正解做法。
首先 \(O(nm^2)\) 的暴力 dp 是显然的,记 \(f_{u,k,0/1}\) 代表点 \(u\) 的子树,重量为 \(k\),是否选了点 \(u\) 的最大值,同时记录 \(g_{u,k,0/1}\) 代表方案数。
观察到每次 \(O(m^2)\) 的合并是非常浪费的,考虑提取出真正有效的 \(\min(2^{siz},m)\) 个状态进行合并,再考虑到长链的情况,对重儿子的状态进行继承。
估算一下,应该一个节点下面吊四条长度为 \(13\) 的链可以卡满,复杂度为 \(O(3m^2)\)。
事实上,这个做法就是把多项式提取出来暴力做 \((\max,+)\) 卷积。
const i32 N = 50 + 5, inf = 1e16, K = 5e3 + 5;
struct node {
i32 val, cnt;
node (i32 a = -inf, i32 b = 0) { val = a, cnt = b; }
friend node operator + (node a, node b) {
i32 val = max(a.val, b.val), cnt = 0;
cnt += (val == a.val ? a.cnt : 0) + (val == b.val ? b.cnt : 0);
return node(val, cnt);
}
friend node operator * (node a, node b) { return node(a.val + b.val, a.cnt * b.cnt); }
friend node operator + (node a, i32 k) { return node(a.val + k, a.cnt); }
} f[N][K][2], g[K][2];
i32 n, V, x, y, siz[N], son[N], w[N], v[N];
vi G[N];
struct info { i32 x; node y; i32 z; };
fv DP(i32 u, i32 fa) {
siz[u] = 1;
for (i32 v : G[u]) {
if (v == fa) continue;
DP(v, u), siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
rep (i, 0, V) f[u][i][0] = f[u][i][1] = node();
f[u][0][0] = node(0, 1), f[u][w[u]][1] = node(v[u], 1);
if (son[u]) {
rep (i, 0, V) f[u][i][0] = f[son[u]][i][0] + f[son[u]][i][1];
drep (i, V, w[u]) f[u][i][1] = f[son[u]][i - w[u]][0] + v[u];
}
vector <info> S;
for (i32 v : G[u]) {
if (v == fa || v == son[u]) continue;
rep (i, 0, V) rep (k, 0, 1) if (f[v][i][k].cnt) S.pb({i, f[v][i][k], k});
rep (i, 0, V) g[i][0] = g[i][1] = node();
rep (i, 0, S.size() - 1) {
i32 w = S[i].x, flg = S[i].z;
node v = S[i].y;
if (!flg) {
drep (i, V, w) g[i][0] = g[i][0] + (f[u][i - w][0] * v);
drep (i, V, w) g[i][1] = g[i][1] + (f[u][i - w][1] * v);
} else {
drep (i, V, w) g[i][0] = g[i][0] + (f[u][i - w][0] * v);
}
}
rep (i, 0, V) f[u][i][0] = g[i][0], f[u][i][1] = g[i][1];
S.clear();
}
}
i32 id = 0;
fv sol() {
id++, cout << "Case " << id << ": \n", cin >> n >> V;
rep (i, 1, n) cin >> w[i] >> v[i];
rep (i, 1, n - 1) cin >> x >> y, G[x].pb(y), G[y].pb(x);
DP(1, 0);
i32 ans = 0;
rep (i, 1, V) cout << (f[1][i][0] + f[1][i][1]).cnt << " ";
cout << "\n";
rep (i, 1, n) G[i].clear();
mem(son, 0);
}
int main() {
IOS; i32 T = 1; cin >> T;
while (T--) sol();
}
LGP6383『MdOI R2』Resurrection
结论是,对于一条链上的从上至下的四个节点 \((x,y,z,w)\),如果 \((x,z)\) 有边,那么 \((y,w)\) 必然无边。
证明:如果 \((x,z)\) 连边,那么 \(z\) 必然在 \(y\) 之前被删除,而如果 \((y,w)\) 也有边,那么 \(z,w\) 必定在 \(y\) 之后删除,矛盾。
所以设 \(f_{u,i}\) 代表节点 \(u\) 上面有 \(i\) 个祖先点可以连边,转移即从上面随意选一个连边。
这样就可以 \(O(n^3)\),观察转移式发现可以前缀和优化,所以是 \(O(n^2)\)。
m64 f[N][N], sum[N][N];
i32 n, u, v;
vi G[N];
fv dfs(i32 u, i32 fa) {
rep (i, 1, n) f[u][i] = 1;
if (u == n) f[u][0] = 1;
for (i32 v : G[u]) {
if (v == fa) continue;
dfs(v, u);
rep (i, 1, n) f[u][i] *= sum[v][i + 1];
}
sum[u][1] = f[u][1];
rep (i, 2, n) sum[u][i] = sum[u][i - 1] + f[u][i];
}
fv sol() {
cin >> n;
U (i, 1, n) cin >> u >> v, G[u].pb(v), G[v].pb(u);
dfs(n, 0); m64 ans = 1;
for (i32 u : G[n]) ans *= f[u][1];
cout << ans << "\n";
}
JOI2012 カンガルー(Kangaroo)
考虑先按外体积大小从大到小排序,这样每个袋鼠都只能塞到前面的袋鼠里面,预处理 \(cnt_i\) 代表 \(i\) 可以赛到前面的多少个里面。
我们将袋鼠分为三类:
- 单个袋鼠。
- 多个袋鼠,且其他的袋鼠还可以塞进去,称为非法情况。
- 多个袋鼠,且其他的袋鼠塞不进去,称为合法情况。
然后进行 dp,设 \(f_{i,j,k}\) 代表考虑到 \(i\),合并成了 \(j\) 个袋鼠,有 \(k\) 个是非法的。转移:
- \(f_{i,j,k} \to f_{i+1,j,k-1} \times k\):将第 \(i\) 个袋鼠塞到非法袋鼠的口袋里面。
- \(f_{i,j,k} \times (cnt_i-(i-j-1)-k) \to f_{i+1,j,k}\):将第 \(i\) 个袋鼠塞到合法袋鼠的口袋里面。
- \(f_{i,j,k} \to f_{i+1,j+1,cnt_i-(i-1-j)}\):第 \(i\) 个袋鼠单独成为一个。
直接转移,空间滚动数组,时间 \(O(n^3)\)。
i32 n, cnt[N];
m64 ans, f[2][N][N];
pii a[N];
int main() {
cin >> n, f[1][0][0] = 1;
rep (i, 1, n) cin >> a[i].fi >> a[i].se;
sort(a + 1, a + 1 + n, greater<pii>());
rep (i, 1, n) U (j, 1, i) if (a[i].fi < a[j].se) cnt[i]++;
rep (i, 1, n) {
rep (j, 0, n) rep (k, 0, n) f[(i + 1) & 1][j][k] = 0;
U (j, 0, i) rep (k, 0, j) {
if (k) f[(i + 1) & 1][j][k - 1] += f[i & 1][j][k] * k;
f[(i + 1) & 1][j][k] += f[i & 1][j][k] * (cnt[i] - (i - 1 - j) - k);
f[(i + 1) & 1][j + 1][cnt[i] - (i - 1 - j)] += f[i & 1][j][k];
}
}
rep (i, 1, n) ans += f[(n + 1) & 1][i][0];
cout << ans;
}
「JOI Open 2016」摩天⼤楼
问题是排列问题,直接做感觉是难以维护的,考虑插入 dp。
顺序不重要,所以从小到大加入每个值,把排列视为 \((i,p_i)\) 点组成的折线,我们把加入过程视作用一根横线往上扫。这个横线下方的形状可以用很多个下凸的谷来描述。用 \(y=a_{i-1}\) 和 \(y=a_{i}\) 的直线去截这个折线,那么每个下凸的谷可以造成两倍贡献。特殊地,如果这个谷和 \(1\) 或者 \(n\) 相连,则每个都需要减去 \(1\)。
所以考虑 dp 来维护这些连续段,假设当前加入到 \(i\),设 \(f_{i,j,k,0/1,0/1}\) 表示有 \(j\) 个连续段,已经造成了 \(k\) 的贡献,\(1\) 是否被确定,\(n\) 是否被确定的方案数。
当前对 \(f_{i,j,k,p,q}\) 转移,记 \(w=2*(a_i-a_{i-1})-p-q\)
- 插入一个新的连续段:\(f_{i,j,k,p,q} \to f_{i+1,j+1,k+w,p,q} \times (j+1-p-q)\)。
- 合并两个连续段:\(f_{i,j,k,p,q} \to f_{i+1,j-1,k+w,p,q} \times (j-1)\)。
- 给一个连续段新增一个端点:\(f_{i,j,k,p,q} \to f_{i+1,j,k+w,p,q} \times (2\times j-p-q)\)。
- 固定端点 \(1\):\(f_{i,j,k,0,q} \to f_{i+1,j,k+w,1,q},f_{i,j,k,0,q} \to f_{i+1,j+1,k,1,q}\)。
- 固定端点 \(n\):\(f_{i,j,k,p,0} \to f_{i+1,j,k+w,p,1},f_{i,j,k,p,0} \to f_{i+1,j+1,k,p,1}\)。
然后答案就是 \(\displaystyle\sum\limits_{i=1}^L f_{n,1,i,1,1}\)。
时间 \(O(n^2L)\)。
m64 f[2][N][V][2][2];
i32 n, L, a[N];
int main() {
cin >> n >> L;
rep (i, 1, n) cin >> a[i];
if (n == 1) return cout << 1, 0;
if (n == 2) return cout << (abs(a[1] - a[2]) <= L) * 2, 0;
sort(a + 1, a + 1 + n);
rep (p, 0, 1) rep (q, 0, 1) if (!(p & q)) f[1][1][0][p][q] = 1;
rep (i, 1, n - 1) {
i32 i1 = (i & 1), i2 = ((i + 1) & 1);
mem(f[i2], 0);
rep (j, 0, i) {
rep (k, 0, L) {
rep (p, 0, 1) {
rep (q, 0, 1) {
if (j == 0 && (p || q)) continue;
i32 w = (j * 2 - p - q) * (a[i + 1] - a[i]);
if (k + w > L) continue;
m64 val = f[i1][j][k][p][q];
if (!p) f[i2][j][k + w][1][q] += val, f[i2][j + 1][k + w][1][q] += val;
if (!q) f[i2][j][k + w][p][1] += val, f[i2][j + 1][k + w][p][1] += val;
f[i2][j + 1][k + w][p][q] += val * (j + 1 - p - q);
if (j > 1) f[i2][j - 1][k + w][p][q] += val * (j - 1);
f[i2][j][k + w][p][q] += val * (j * 2 - p - q);
}
}
}
}
}
m64 ans = 0;
rep (i, 0, L) ans += f[n & 1][1][i][1][1];
cout << ans;
}
「ABC266Ex」Snuke Panic (2D)
设 \(f_i\) 为到达 \(i\),并且成功选择了 \(i\) 的最大价值,考虑 \(i \to j\)。
发现需要满足几个条件:
- \(Y_i \le Y_j,|X_i-X_j|+Y_j-Y_i+T_i\le T_j\)。
拆开绝对值,分成两部分:
- \(X_i \le X_j, Y_i \le Y_j, T_i-X_i-Y_i \le T_j-X_j-Y_j\)。
- \(X_i \ge X_j, Y_i \le Y_j, T_i+X_i-Y_i \le T_j+X_j-Y_j\)。
这样就比较简单了,直接树套树 / cdq 转移就好了,这里实现采用的树套树。
时间 \(O(n \log^2 n)\)。
i32 n;
struct SGT {
i32 s[N << 8], ls[N << 8], rs[N << 8], rt[N << 2], tot;
fv upd2(i32 &p, i32 x, i32 k, i32 l = 1, i32 r = n) {
if (x > r || x < l) return ;
if (!p) p = ++tot;
if (l == r) return (void)(s[p] = max(s[p], k));
i32 mid = (l + r) >> 1;
upd2(ls[p], x, k, l, mid), upd2(rs[p], x, k, mid + 1, r);
s[p] = max(s[ls[p]], s[rs[p]]);
}
fv upd1(i32 p, i32 x, i32 y, i32 k, i32 l = 1, i32 r = n) {
if (x > r || x < l) return ;
upd2(rt[p], y, k);
if (l == r) return ;
i32 mid = (l + r) >> 1;
upd1(p << 1, x, y, k, l, mid), upd1(p << 1 | 1, x, y, k, mid + 1, r);
}
i32 qry1(i32 p, i32 ql, i32 qr, i32 l = 1, i32 r = n) {
if (ql > r || qr < l || !p) return -inf;
if (ql <= l && r <= qr) return s[p];
i32 mid = (l + r) >> 1;
return max(qry1(ls[p], ql, qr, l, mid), qry1(rs[p], ql, qr, mid + 1, r));
}
i32 qry2(i32 p, i32 ql, i32 qr, i32 L, i32 R, i32 l = 1, i32 r = n) {
if (ql > r || qr < l || ql > qr) return -inf;
if (ql <= l && r <= qr) return qry1(rt[p], L, R);
i32 mid = (l + r) >> 1;
return max(qry2(p << 1, ql, qr, L, R, l, mid), qry2(p << 1 | 1, ql, qr, L, R, mid + 1, r));
}
} A, B;
struct node {
i32 T, X, Y, A;
} t[N];
fn bool cmp(node a, node b) {
return a.Y == b.Y ? a.T < b.T : a.Y < b.Y;
}
i32 f[N], dx[N], dy[N], dt1[N], dt2[N], ans, cx, cy, ct1, ct2;
int main() {
cin >> n;
rep (i, 1, n + 1) {
if (i > 1) cin >> t[i].T >> t[i].X >> t[i].Y >> t[i].A;
else t[i] = {0, 0, 0, 0};
dx[++cx] = t[i].X, dy[++cy] = t[i].Y;
dt1[++ct1] = -t[i].X - t[i].Y + t[i].T, dt2[++ct2] = t[i].X - t[i].Y + t[i].T;
}
n++;
sort(dx + 1, dx + 1 + cx), sort(dy + 1, dy + 1 + cy);
sort(dt1 + 1, dt1 + 1 + ct1), sort(dt2, dt2 + 1 + ct2);
cx = unique(dx + 1, dx + 1 + cx) - dx - 1;
cy = unique(dy + 1, dy + 1 + cy) - dy - 1;
ct1 = unique(dt1 + 1, dt1 + 1 + ct1) - dt1 - 1;
ct2 = unique(dt2 + 1, dt2 + 1 + ct2) - dt2 - 1;
sort(t + 1, t + 1 + n, cmp);
rep (i, 1, n) {
auto [T, X, Y, val] = t[i];
i32 x = ld(dx + 1, dx + cx + 1, X) - dx;
i32 y = ld(dt1 + 1, dt1 + ct1 + 1, -X - Y + T) - dt1;
i32 z = ld(dt2 + 1, dt2 + ct2 + 1, X - Y + T) - dt2;
f[i] = max(f[i], A.qry2(1, 1, x, 1, y) + val);
f[i] = max(f[i], B.qry2(1, x, cx, 1, z) + val);
A.upd1(1, x, y, f[i]), B.upd1(1, x, z, f[i]);
ans = max(ans, f[i]);
}
cout << ans;
}
「UVA10304」Optimal Binary Search Tree
考虑区间 dp,设 \(f_{l,r}\) 代表区间 \([l,r]\) 的答案,枚举断点 \(k\),得到转移式:\(f_{i,j} = \min(f_{i,k-1}+f_{k+1,j}+\sum\limits_{z=i}^je_z-e_k)\)
打表发现满足决策单调性,故直接四边形不等式优化就可以做到 \(O(n^2)\)。
i32 n, L, a[N], sum[N], f[N][N], g[N][N];
int main() {
IOS;
while (cin >> n) {
mem(f, 0x3f);
rep (i, 1, n) cin >> a[i], sum[i] = sum[i - 1] + a[i];
rep (i, 0, n) f[i][i] = f[i + 1][i] = 0, g[i][i] = i;
rep (len, 2, n) {
rep (i, 1, n - len + 1) {
i32 j = i + len - 1;
rep (k, g[i][j - 1], g[i + 1][j]) {
i32 cur = f[i][k - 1] + f[k + 1][j] + (sum[j] - sum[i - 1] - a[k]);
if (f[i][j] > cur) f[i][j] = cur, g[i][j] = k;
}
}
}
cout << f[1][n] << "\n";
}
}
「ARC150F」Constant Sum Subsequence
首先考虑一个暴力 dp,设 \(f_i\) 为所有组成 \(i\) 的方案中的右端点的最大值,记 \(\text{nxt}(i,j)\) 为 \(i\) 后面 \(j\) 的第一个出现位置。
暴力 dp 转移比较简单,\(f_i = \max\limits_{j=1}^i(\text{nxt}(f_{i-j}, j))\)。答案即为 \(f_n\)。
关于这个 dp 的正确性:dp 的过程类似于一个背包过程,对于可以组成 \(i\) 的每一种可能的子序列均会在某种转移中出现。
注意到,\(f\) 的转移是一个半在线的形式,这显然是不利于我们优化的,所以说考虑用 cdq 给离线处理。
现在考虑 \([L,M]\) 对 \([M+1,R]\) 的转移,我们考虑枚举 \(j\) 去转移。
首先 \(\text{nxt}(i_1,j) \le \text{nxt}(i_2,j)(i_1 < i_2)\),取到等号当且仅当 \((i_1,i_2]\) 不存在 \(j\)。
观察 \(f_{i,j}\) 贡献有效的区域,记 \(m=\min(M,R-j)\),那么当且仅当 \(\text{nxt}(i,j)=\text{nxt}(m,j)\) 时,贡献才有效。
正确性证明:\(\text{nxt}(i,j) \ne \text{nxt}(m,j)\) 时,\(\text{nxt}(i,j) \le m < \text{nxt}(m,i+j-m)\),也就是说对于 \(f_{i+j}\) 而言,从 \(f_m\) 转移过来的一定更优。
继续注意到 \(\text{nxt}(i,j)\) 关于 \(i\) 单调,所以找到这个与 \(\text{nxt}(m,j)\) 相等的区间的左端点,直接区间取 \(\max\),单点查询,总复杂度 \(O(n \log^2 n)\)。
const i32 N = 1.5e6 + 5, inf = 1e16;
i32 n;
struct SGT {
i32 s[N << 2];
fv upd(i32 p, i32 ql, i32 qr, i32 k, i32 l = 1, i32 r = n) {
if (ql > qr || ql > r || qr < l) return ;
if (ql <= l && r <= qr) return (void)(s[p] = max(s[p], k));
i32 mid = (l + r) >> 1;
upd(p << 1, ql, qr, k, l, mid), upd(p << 1 | 1, ql, qr, k, mid + 1, r);
}
i32 qry(i32 p, i32 x, i32 l = 1, i32 r = n) {
if (l > x || r < x) return -inf;
if (l == r) return s[p];
i32 mid = (l + r) >> 1;
return max({s[p], qry(p << 1, x, l, mid), qry(p << 1 | 1, x, mid + 1, r)});
}
} T;
i32 a[N], f[N], S;
vi vec[N];
fn i32 nxt(i32 i, i32 j) {
i32 k = i % n, cnt = i / n * n;
return (vec[j].back() <= k ? vec[j].front() + n : *ud(ALL(vec[j]), k)) + cnt;
}
fn i32 pre(i32 i, i32 j) {
i32 k = i % n, cnt = i / n * n;
return (k <= vec[j].front() ? vec[j].back() - n : *(--ld(ALL(vec[j]), k))) + cnt;
}
fv sol(i32 l, i32 r) {
if (l == r) return;
i32 mid = (l + r) >> 1;
sol(l, mid);
rep (k, 1, r - l) {
i32 R = min(r - k, mid);
i32 L = ld(f + l, f + R + 1, pre(f[R] + 1, k)) - f;
L = max(L + k, mid + 1), R += k;
if (L <= R) T.upd(1, L, R, nxt(f[R - k], k));
}
rep (i, mid + 1, r) f[i] = T.qry(1, i);
sol(mid + 1, r);
}
int main() {
cin >> n >> S;
rep (i, 1, n) cin >> a[i], vec[a[i]].push_back(i);
sol(0, S), cout << max(T.qry(1, S), 1ll);
}
CF1363F Rotating Substrings(*2600)
把题目的操作看作是把一个字符前移到任意位置,我们倒着来分析这个问题,设在 \(S\) 中 \(T_n\) 最后一次出现的下标为 \(p\),那么显然会把 \([p+1,n]\) 的所有字符全部前移,位置自定,这样才可以让最后一位匹配。
自然而然,我们考虑对这个过程进行逐位 dp,设 \(f_{i,j}\) 代表 \(S[i:n]\) 已经经过若干次操作与 \(T[j:n]\) 匹配上的最小代价。
- \(f_{i,j} \to f_{i-1,j-1}(S_{i-1}=T_{j-1})\):直接匹配 \(i-1\) 和 \(j-1\)。
- \(f_{i,j} \to f_{i,j-1}(\displaystyle\sum\limits_{k=i}^n[S_k=T_{j-1}] > \sum\limits_{k=j}^n[T_k=T_{j-1}])\):选择之前已经被后移的一个 \(S_x\) 与 \(T_{j-1}\) 匹配。
- \(f_{i,j} \to f_{i-1,j}+1\):直接把 \(S_i\) 后移。
这样就做完了,时间复杂度 \(O(n^2)\)。
string s, t;
i32 n, sums[30][N], sumt[30][N], f[N][N];
fv sol() {
cin >> n >> t >> s, n = s.length(), s = " " + s, t = " " + t;
rep (i, 0, 25) {
rep (j, 1, n) sums[i][j] = sums[i][j - 1] + (s[j] - 'a' == i);
rep (j, 1, n) sumt[i][j] = sumt[i][j - 1] + (t[j] - 'a' == i);
if (sums[i][n] != sumt[i][n]) return (void)(cout << "-1\n");
}
rep (i, 1, n + 1) rep (j, 1, n + 1) f[i][j] = inf;
f[n + 1][n + 1] = 0;
drep (i, n + 1, 1) {
drep (j, i, 1) {
f[i][j - 1] = min(f[i][j - 1], f[i][j] + 1);
if (s[i - 1] == t[j - 1]) f[i - 1][j - 1] = min(f[i - 1][j - 1], f[i][j]);
if (i == 1) continue;
i32 k = s[i - 1] - 'a';
if (sumt[k][n] - sumt[k][j - 1] > sums[k][n] - sums[k][i - 1]) f[i - 1][j] = min(f[i - 1][j], f[i][j]);
}
}
cout << f[1][1] << "\n";
}
int main() {
IOS;
i32 T = 1; cin >> T;
while (T--) sol();
}
CF1799G Count Voting(*2600)
首先,对原问题进行容斥,设 \(g_i\) 为钦定 \(i\) 票是自己阵营投向自己阵营的方案数,答案即为 \(\displaystyle\sum\limits_{i=0}^n(-1)^ig_i\)。
再考虑如何算这个 \(g_i\),我们考虑把每一张票看作一个物品,这里有个技巧就是,把投给一个人的所有票也看作是有顺序的,这样就可以直接对票的排列计数,最后再乘上 \(\displaystyle\prod\dfrac{1}{c_i!}\) 就好。
记阵营 \(i\) 需要 \(T_i\) 票,有 \(S_i\) 人,设 \(f_{i,j}\) 代表考虑前 \(i\) 个阵营,已经钦定了 \(j\) 个的方案数,有转移:
分析后发现复杂度是 \(O(n^2)\) 的,复杂度证明类似树上背包。
i32 n, lim, c[N], t[N], S[N], T[N];
m64 ans, fac[N], ifac[N], f[N][N];
fn m64 qkpow(m64 a, i32 b, m64 ans = 1) {
for (; b; b >>= 1) { if (b & 1) ans *= a; a *= a; }
return ans;
}
fn m64 C(i32 n, i32 m) {
return fac[n] * ifac[m] * ifac[n - m];
}
int main() {
cin >> n;
fac[0] = 1; m64 sum = 1;
rep (i, 1, n) fac[i] = fac[i - 1] * i;
ifac[n] = qkpow(fac[n], mod - 2);
drep (i, n - 1, 0) ifac[i] = ifac[i + 1] * (i + 1);
rep (i, 1, n) cin >> c[i], sum /= fac[c[i]];
rep (i, 1, n) cin >> t[i], S[t[i]]++, T[t[i]] += c[i];
f[0][0] = 1;
rep (i, 1, n) {
if (!S[i]) {
cpy(f[i], f[i - 1]);
continue;
}
lim += min(S[i], T[i]);
rep (j, 0, lim) {
rep (k, 0, min({j, S[i], T[i]})) {
f[i][j] += f[i - 1][j - k] * C(S[i], k) * C(T[i], k) * fac[k];
}
}
}
rep (i, 0, n) ans += (fac[n - i] * f[n][i] * sum * (i & 1 ? (m64)mod - 1 : 1));
cout << ans;
}
「ABC134F」Permutation Oddness
考虑拆开绝对值,则 \(i\) 的贡献取决于和 \(p_i\) 的相对大小。
分析一下,把 \(i\) 和 \(p_i\) 看作两排点,问题等价于求匹配的数量,所以我们设 \(f_{i,j,k}\) 代表遍历到 \(i\),前面有 \(j\) 个位置往后预定匹配,当前代价为 \(k\)。
单个点的代价有且仅与它往前还是往后匹配有关,容易得出转移:
- \(f_{i,j,k} \times j^2 \to f_{i+1,j-1,k+2(i+1)}\):\(i\) 和 \(p_i\) 全部向前匹配,每个都有 \(j\) 个匹配方案。
- \(f_{i,j,k} \to f_{i+1,j+1,k-2(i+1)}\):\(i\) 和 \(p_i\) 全部向后匹配。
- \(f_{i,j,k} \to f_{i+1,j,k}\):\(i\) 和 \(p_i\) 进行匹配。
- \(f_{i,j,k} \times 2j \to f_{i+1,j,k}\):\(i\) 和 \(p_i\) 一个往前一个往后匹配。
时间复杂度 \(O(n^4)\)。
const i32 V = 55 * 55;
i32 n, K;
m64 f[N][N][N * N * 2];
int main() {
cin >> n >> K;
f[0][0][V] = 1;
rep (i, 0, n - 1) {
rep (j, 0, i) {
rep (k, -V, V) {
if (j && k + 2 * (i + 1) <= n * n) f[i + 1][j - 1][k + 2 * (i + 1) + V] += f[i][j][k + V] * j * j;
if (k - 2 * (i + 1) >= -n * n) f[i + 1][j + 1][k - 2 * (i + 1) + V] += f[i][j][k + V];
f[i + 1][j][k + V] += f[i][j][k + V] * (2 * j + 1);
}
}
}
cout << f[n][0][K + V];
}
「ARC125F」Tree Degree Subset Sum
首先树保证了 \(\displaystyle \sum d_i =2n - 2\),首先考虑给全部 \(d_i \to d_i-1\),这样度数总和就是 \(n-2\)。
先考虑从树的性质上去挖掘,发现挖掘的性质并不能很好地利用。所以抛弃掉树的形态。
我们考虑固定 \(x\) 或者 \(y\)。发现固定 \(y\) 时,对应的所有合法的 \(x\) 时连续的。
设 \(L_y,R_y\) 代表最小和最大的 \(x\),记 \(k=\displaystyle\sum\limits_{i=1}^n[d_i=0]\),显然 \(R_y\) 一定会把所有 \(0\) 加入,而 \(L_y\) 一定不会加入任何一个 \(0\)。所以 \(x \in [L_y,L_y+k]\bigcup [R_y-k,R_y]\) 的方案都是合法的。
- 结论:\(R_y-L_y \le 2k+1\)。
证明:一定存在 \(y-x \ge -k\),因为只有 \(d_i=0\) 的点会让左边 \(-1\),取出 \(x\) 的补集,和为 \(n-2-y\),带入上面的式子,得到 \(y-x \le k-2\)。所以 \(y-x \in [-k,k-2]\),得证。
所以,求出 \(L_i,R_i\) 即可,由于 \(d_i\) 只有 \(O(\sqrt n)\) 种,直接单调队列优化多重背包就可以做到 \(O(n \sqrt n)\)。
const i32 N = 2e5 + 5;
i32 n, u, v, ans, head, tail, i0, i1, Q[N], deg[N], cnt[N], f[2][N], L[N], R[N];
#define pos(x) (x * i + j)
int main() {
cin >> n;
U (i, 1, n) cin >> u >> v, deg[u]++, deg[v]++;
rep (i, 1, n) deg[i]--, cnt[deg[i]]++;
mem(f[0], 0xcf), f[0][0] = cnt[0];
i0 = 0, i1 = 1;
for (i32 i = 1; i <= n; i++, swap(i0, i1)) {
if (!cnt[i]) {
swap(i0, i1);
continue;
}
i32 m = cnt[i];
rep (j, 0, i - 1) {
head = 1, tail = 0;
rep (k, 0, n / i) {
if (pos(k) > n) break;
while (head <= tail && Q[head] < k - m) head++;
while (head <= tail && f[i0][pos(k)] > f[i0][pos(Q[tail])] + k - Q[tail]) tail--;
Q[++tail] = k;
f[i1][pos(k)] = f[i0][pos(Q[head])] + k - Q[head];
}
}
}
rep (i, 0, n) R[i] = f[i0][i];
mem(f[0], 0x3f), f[0][0] = 0;
i0 = 0, i1 = 1;
for (i32 i = 1; i <= n; i++, swap(i0, i1)) {
if (!cnt[i]) {
swap(i0, i1);
continue;
}
i32 m = cnt[i];
rep (j, 0, i - 1) {
head = 1, tail = 0;
rep (k, 0, n / i) {
if (pos(k) > n) break;
while (head <= tail && Q[head] < k - m) head++;
while (head <= tail && f[i0][pos(k)] < f[i0][pos(Q[tail])] + k - Q[tail]) tail--;
Q[++tail] = k;
f[i1][pos(k)] = f[i0][pos(Q[head])] + k - Q[head];
}
}
}
rep (i, 0, n) L[i] = f[i0][i], ans += max(0ll, R[i] - L[i] + 1);
cout << ans;
}
CF1152F2 Neko Rules the Catniverse (Large Version)(*3000)
首先对于限制一,从下标去加入是难以刻画的,考虑从值域出发 dp。
对第三个限制进行转化,对于每个 \(i\) 的合法条件等价于,记 \(j\) 是 \(i\) 左边第一个满足 \(a_j < a_i\) 的下标,则 \(i\) 需要满足 \(a_i \le a_j+m\)。
做插入 dp,按 \(1\) 到 \(n\) 依次选择是否加入 \(i\),由于需要知道是否有 \([i-m,i-1]\) 中的转移点,所以考虑状压下来 \([i-m,i-1]\) 是否加入的状态 \(S\),得出 dp 状态:\(f_{i,j,S}\) 代表考虑到 \(i\),选了 \(j\) 个,前 \(m\) 个的状态为 \(S\) 的方案数。
-
\(f_{i,j,S} \times (\text{popcount}(S)+1)\to f_{i+1,j+1,(2S+1) \& (2^m-1)}\):选择 \(i\),则一共有 \(\text{popcount}(S)\) 个转移点,加上 \(i\) 可以放开头。
-
\(f_{i,j,S} \to f_{i+1,j,(2S) \& (2^m-1)}\):不选择 \(i\)。
转移与 \(i\) 无关,直接矩阵快速幂优化即可,时间复杂度 \(O(k^3 8^m \log n)\)。
const i32 N = 200 + 5, mod = 1e9 + 7;
i32 n, k, m, L;
struct mat {
i32 a[N][N];
mat() { mem(a, 0); }
i32* operator [](i32 x) { return a[x]; }
friend mat operator * (mat a, mat b) {
mat c;
rep (i, 0, L) rep (k, 0, L) rep (j, 0, L) c[i][j] = (c[i][j] + a[i][k] * b[k][j] % mod) % mod;
return c;
}
} a;
fn mat qkpow(mat a, i32 b) {
mat ans; ans[0][0] = 1;
for (; b; b >>= 1) {
if (b & 1) ans = ans * a;
a = a * a;
}
return ans;
}
int main() {
cin >> n >> k >> m, L = k << m;
U (i, 0, k) U (S, 0, (1 << m)) {
i32 msk = (S << 1) & ((1 << m) - 1), w = bp(S) + 1;
a[(i << m) + S][(i << m) + msk] = 1;
if(i == k - 1) a[(i << m) + S][L] = w;
else a[(i << m) + S][((i + 1) << m) + msk + 1] = w;
}
a[L][L] = 1;
cout << qkpow(a, n)[0][L];
}
CF1242C Sum Balance(*2400)
首先可以求出来所有数的和,如果模 \(k\) 不等于 \(0\),一定无解。
这样就可以得到每个盒子的目标总数 \(s\),考虑对每个整数 \(x\) 建点,可以得到限制:如果拿出去 \(x\),那么一定要进来 \(y\)。如果不存在 \(y\),则不能拿出去 \(x\)。
考虑图论建模,连边 \(x \to y\),这样形成的是一个内向基环树森林,如果环上所有点的所属盒子不同,则环上的所有点均可以满足限制。
所以状压,基环树找环,最后再合并状态就好了,写的子集枚举,时间复杂度 \(O(3^n)\)。
const i32 N = 1e5 + 5, mod = 1e9 + 7;
i32 k, res, cnt, sum[N], n[N], vis[1 << 15], fr[1 << 15], f[1 << 15];
pii t[1 << 15], ans[20];
vi a[N], col[N];
vector <pii> g[N];
map <i32, pii> T;
fv findring(i32 x, i32 y) {
while (~g[x][y].fi) {
auto [fx, fy] = g[x][y];
x = fx, y = fy;
if (col[x][y] == cnt) {
i32 msk = 0, flg = 0, tx = x, ty = y;
do {
if ((msk >> x) & 1) {
flg = 1; break;
}
msk |= (1 << x);
auto [fx, fy] = g[x][y];
x = fx, y = fy;
} while (x != tx || y != ty) ;
if (!flg) f[msk] = vis[msk] = 1, t[msk] = {x, y};
break;
} else if (col[x][y]) break;
col[x][y] = cnt;
}
}
fv getplan(i32 msk) {
if (vis[msk]) {
auto [x, y] = t[msk];
do {
auto [fx, fy] = g[x][y];
ans[fx] = {a[fx][fy], x};
x = fx, y = fy;
} while (x != t[msk].fi);
} else getplan(fr[msk]), getplan(msk ^ fr[msk]);
}
int main() {
cin >> k;
U (i, 0, k) {
cin >> n[i]; a[i].resize(n[i] + 5), g[i].resize(n[i] + 5);
col[i].resize(n[i] + 5);
U (j, 0, n[i]) cin >> a[i][j], sum[i] += a[i][j], T[a[i][j]] = {i, j};
res += sum[i];
}
if (res % k) return cout << "No", 0;
res /= k;
U (i, 0, k) U (j, 0, n[i]) {
i32 x = res - sum[i] + a[i][j];
if (T.count(x)) g[i][j] = T[x];
else g[i][j].fi = -1;
}
U (i, 0, k) U (j, 0, n[i]) if (!col[i][j]) col[i][j] = ++cnt, findring(i, j);
f[0] = 1;
U (S, 1, (1 << k)) for (i32 T = S; T; T = (T - 1) & S)
if (f[T] && f[S ^ T]) { f[S] = 1, fr[S] = T; break; }
if (!f[(1 << k) - 1]) return cout << "No", 0;
getplan((1 << k) - 1);
cout << "Yes\n";
U (i, 0, k) cout << ans[i].fi << " " << ans[i].se + 1 << "\n";
}
LGP11325 【MX-S7-T3】「SMOI-R2」Monotonic Queue
首先是区间不用达到 \(n\) 个,因为可以塞进去若干个 \([1,1]\)。
考虑用移动 \(l\) 去避免负数 \(c_i\) 的贡献,发现对于一段负数,最好的方式是以 \([x,x],[x+1,x+1],...[x+m,x+m]\) 的形式移动 \(l,r\)。
再考虑如何选中 \(x\) 的贡献,需要找到右边第一个 \(y\) 使得 \(a_y>a_x\),然后加入 \([x+1,y-1]\),最后加入 \(y\) 弹出 \([x,y-1]\) 并对这个区间的所有数算贡献。
对上面的两种方法 dp 维护,第一种是单步转移,第二种是断点转移,维护出第二种的单调栈即可,时间 \(O(n)\)。
const i32 N = 5e5 + 5, mod = 1e9 + 7;
i32 stk[N], c[N], a[N], sum[N], R[N], f[N], ans, n, tp;
int main() {
cin >> n;
rep (i, 1, n) cin >> c[i], sum[i] = sum[i - 1] + c[i];
mem(f, 0xcf), f[1] = 0;
rep (i, 1, n) cin >> a[i];
drep (i, n, 1) {
while (tp && a[stk[tp]] < a[i]) tp--;
R[i] = stk[tp], stk[++tp] = i;
}
rep (i, 1, n) {
ans = max(ans, f[i]);
f[i + 1] = max(f[i + 1], f[i] + (a[i] < a[i + 1]) * c[i]);
if (R[i]) f[R[i]] = max(f[R[i]], f[i] + sum[R[i] - 1] - sum[i - 1]);
}
cout << ans;
}
CF1392H ZS Shuffles Cards(*3000)
首先是关于这个期望的转化:
\(E(游戏结束时期望抽牌数) = \displaystyle\sum_i P(游戏在\ i-1\ 轮没有结束) \times E(游戏第 \ i \ 轮的抽牌数) = \displaystyle\sum_i P(游戏在\ i-1\ 轮没有结束) \times E(游戏一轮的抽牌数) = E(轮数)E(游戏一轮的抽牌数)\)。
后面一项由于鬼牌在数字牌后面的概率是 \(\dfrac{1}{m+1}\),所以期望值是 \(\dfrac{n}{m+1}+1\)。
现在考虑前面一项,设 \(f_k\) 代表还差 \(k\) 张卡牌的期望轮数,容易列出转移,化简后得到 \(f_k = f_{k-1}+\dfrac{m}{k}\)。
所以答案就是 \((\dfrac{n}{m+1}+1)(mH(n)+1)\)。
i32 n, m;
m64 ans, fac[N], ifac[N], inv[N];
int main() {
cin >> n >> m, fac[0] = inv[0] = 1;
rep (i, 1, n + m) fac[i] = fac[i - 1] * i;
ifac[n + m] = fac[n + m].inv();
drep (i, n + m - 1, 0) ifac[i] = ifac[i + 1] * (i + 1), inv[i + 1] = ifac[i + 1] * fac[i];
rep (i, 1, n) ans += inv[i];
cout << (ans * m + 1) * (n * inv[m + 1] + 1);
}
「LGP8321 JROI-4」沈阳大街 2
有了前面我们处理 ABC134F 的经验,这道题应该是非常简单的。
首先,原问题是求两个排列的所有配对方案的代价和。观察到类似 ABC134F,每个点的权值是 \(0\) 或者 \(a_i/b_i\),而且只与与其配对的那个点的相对大小有关。
继续注意到此题并不保证对于 \(i\),往前面找一个 \(j\) 就一定满足 \(b_j<a_i\),所以考虑合并成一个序列然后 dp。
这样就是非常简单的了。从大往小排序,记 \(f_{i,j,k}\) 代表当前加入到 \(i\),前面有 \(j\) 个红色,\(k\) 个蓝色未匹配。继续注意到 \(j-k\) 为定值,所以可以直接省去第三维,然后同义的状态是 \(f_{i,j}\) 代表加入到 \(i\),匹配了 \(j\) 对的方案数。
const i32 N = 5e3 + 5, mod = 998244353;
i32 n, f[2][N], sum[N * 2];
pii a[N * 2];
fn i32 qkpow(i32 a, i32 b) {
i32 ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
}
return ans;
}
int main() {
cin >> n;
rep (i, 1, n) cin >> a[i].fi, a[i].se = 1;
rep (i, 1, n) cin >> a[i + n].fi, a[i + n].se = 2;
sort(a + 1, a + n * 2 + 1, greater <pii>());
f[0][0] = 1; i32 i0 = 0, i1 = 1;
rep (i, 1, n * 2) sum[i] = sum[i - 1] + (a[i].se == 1);
rep (i, 1, n * 2) {
f[i1][0] = f[i0][0];
rep (j, 1, n) {
f[i1][j] = (f[i0][j] + f[i0][j - 1] * a[i].fi % mod * ((a[i].se == 1 ? i - sum[i] : sum[i]) - (j - 1)) % mod) % mod;
}
swap(i0, i1), mem(f[i1], 0);
}
i32 fac = 1;
rep (i, 1, n) fac = fac * i % mod;
cout << f[i0][n] * qkpow(fac, mod - 2) % mod;
}
CF2115C Gellyfish and Eternal Violet(*2700)
首先考虑最优操作的做法:
- 如果抽到单体攻击:
- 如果序列中 \(\min h_i \ne \max h_i\),那么攻击那个 \(h_i\) 最大的一定最优。
- 否则需要考虑是否攻击
- 如果抽到群体攻击:
- 如果 \(\min h_i > 1\),那么一定攻击,否则不攻击。
首先有 \(O(nmV^2)\) 的暴力 dp,记 \(f_{i,j,k}\) 代表第 \(i\) 个回合,当前最小值为 \(j\),所有元素的和为 \(jn + k\) 的概率。转移:
- \(f_{i,j,0} = (1-p)\max(f_{i-1,j-1,n-1},f_{i-1,j,0}) + pf_{i-1,j-1,0}\):当 \(k=0\) 时,每次非闪光攻击可以考虑是否要攻击。
- \(f_{i,j,k}=pf_{i,j-1,k}+(1-p)f_{i,j,k-1}\):按照最优策略攻击。
决策的分支选项在单体攻击且所有元素相等上面,很自然的想法是先操作到所有数相等,再进行 dp。
不难推出答案就等于 \(\displaystyle\sum_i(1-p)^sp^{i-s}\binom{i-1}{s-1} f_{m-i,\max(x-i+s,1),0}\)。
- 对于组合数和幂次的处理:预处理 \(\ln(n!)\),算出组合数乘上幂次的 \(\ln\),再求 \(\exp\)。
然后做完了,时间复杂度 \(O(nmV)\),略卡常,不要开 long double。
const i32 N = 20 + 5, V = 4e2 + 5, M = 4e3 + 5, inf = 1e16;
db f[M][V][N], fac[M], p, Lp, Lp2, ans;
i32 n, m, s, x, h[N];
db g(i32 n, i32 m) {
return exp(fac[n - 1] - fac[m - 1] - fac[n - m] + (n - m) * Lp + m * Lp2);
}
fv sol() {
cin >> n >> m >> p;
rep (i, 1, n) cin >> h[i];
if (!p) return (void)(cout << "1\n");
if (p == 100) {
bool flg = 1;
rep (i, 2, n) flg &= (h[i] == h[i - 1]);
cout << (flg ? "1\n" : "0\n");
return ;
}
p /= 100, x = 400;
Lp = log(p), Lp2 = log(1 - p), ans = s = 0;
rep (i, 1, n) x = min(x, h[i]);
rep (i, 1, n) s += h[i] - x;
rep (i, 1, m) {
rep (j, 1, x) {
if (j > 1) f[i][j][0] = f[i - 1][j - 1][0] * p + (1 - p) * max(f[i - 1][j][0], f[i - 1][j - 1][n - 1]);
U (k, 1, n) f[i][j][k] = f[i - 1][j - (j > 1)][k] * p + f[i - 1][j][k - 1] * (1 - p);
}
}
if (s < n) return (void)(cout << f[m][x][s] << "\n");
rep (i, s, m) ans += f[m - i][max(x - i + s, 1ll)][0] * g(i, s);
cout << fixed << setprecision(10) << ans << "\n";
}
int main() {
rep (i, 0, 4000) f[i][1][0] = 1;
rep (i, 1, 4000) fac[i] = fac[i - 1] + log(i);
i32 T = 1; cin >> T;
while (T--) sol();
}
「CF1874G」Jellyfish and Inscryption(*3500)
先来考虑链怎么做。
应该会找到一个位置,并且后面 \(2,3\) 操作所有的贡献会全部加到这个点上,设这个点为 \(x\),以 \(x\) 为界把问题分为前后两部分。
\(10^9\) 可以直接看成 \(\infty\),所以应该以 \(\max a_ib_i\) 为第一关键字,\(\sum\limits_{i\ne x} a_ib_i\) 为第二关键字取最大答案。
\(x\) 后面的部分是简单的,考虑 \([1,x-1]\) 的部分怎么做。
假设如果没有 \(2\) 操作,那么 \(a_i\) 是固定的,每次进行 \(3\) 操作时,$ b_i$ 的贡献必定会加到 \([1,i-1]\) 中最大的 \(a_j\) 上面,而且这个 \(j\) 应该是单调不减的。
现在再考虑 \(2,3\) 操作都有的情况,类似上面,\(2,3\) 操作的决策点都是单调不降的。下称序列 \(S\) 的决策点代表 \(S\) 目前的前缀最大值位置。由于我们并不关心决策点的位置,只关心值。我们定义 \(f_{i,x,y,0/1}\) 代表遍历到 \(i\),\(a\) 序列决策位置的值为 \(a_j=x\),\(b\) 序列决策位置的值为 \(b_k=y\),是否有 \(x=y\),这样得到了一个 \(O(n^3V^2)\) 的 dp。
- 注意到如果 \(x>V\) 且 \(y>V\),那么目前位置就没有 dp 的必要了,直接算第二部分贡献就好了。
证明:设 \(a,b\) 当前决策点下标为 \(j,k\),不妨设 \(j<k\),那么选择 \(k\) 并计算第二部分贡献一定优于后面的任意一个下标,因为后面下标不可能 \(>V\),此时 dp 就是无效的。
所以我们可以 \(a_i=x/b_i=y\) 一维只开到 \(V\),做两次,这样 dp 是 \(O(nV^3)\)。
我们如果知道最后 \(a_i\) 的最终变化结果 \(a_i’\),那么对于每个点的决策应该是固定的。
不妨设 \(a_i \le V\),考虑直接对 \(a'\) 序列做 dp,我们设 \(f_{i,j,k}\) 表示遍历到 \(i\),\(\max\limits_{z=1}^{i-1}a'_z=j\),上一个决策点 \(p\) 目前满足 \(a'_p-a_p=k\)。转移:
-
\(op_i=1\):
- \(f_{i-1,j,k}+a_ib_i \to f_{i,\max(j,a_i),k}\):\(i\) 不成为决策点,直接把 \(i\) 加入贡献。注意这里下标取 \(\max\) 的原因是,这里的决策点实际上是预支了贡献,序列的实际决策点可能已经变化到了 \(i\),但是仍然需要把 \(i\) 后面的 \(2\) 操作拿去填 \(p\) 预支的贡献。
- \(f_{i-1,j,0}+k b_i \to f_{i,\max(j,k),k-a_i}\):让 \(i\) 成为决策点并预支 \(i\) 的贡献。关于这里从 \(f_{i-1,j,0}\) 转移的解释:这里的第三维可以看作是对决策点的一种限制,如果不变成第三维 \(0\),没有填补完上一次预支的贡献就不能转移。
-
\(op_i=2\):\(f_{i-1,j,k} \to f_{i,j,k-x_i}\):填补之前决策点的 \(a_p\)。
-
\(op_i=3\):\(f_{i-1,j,k}+jy_i \to f_{i,j,k}\):选择目前的决策点算贡献。
-
\(op_i=0\):\(f_{i-1,j,k} \to f_{i,j,k}\) 直接继承状态。
-
\(op_i=4\):\(f_{i-1,j,k} + w_i \to f_{i,j,k}\) 直接继承状态并加上道具的贡献。
这样就得到了一个 \(O(nV^2)\) 的链上做法。
接下来考虑 DAG 上,发现转移是不变的,仍然这么做,不过需要处理出来每个点作为第一部分结束点可以造成的贡献。记 \(g_{i,j}\) 代表以 \(i\) 作为第一部分结束点,\(\sum a_k\) 为 \(j\) 时,以 \(\sum b_k\) 为第一关键字,\(\sum a_kb_k\) 为第二关键字的最大值,这个容易 \(O(mV^2)\) 处理出来。
最后的细节:特判不存在操作 \(1\) 的情况,跑一遍操作 \(4\) 的最长路就行。
时间复杂度 \(O(nV^2)\)。
const i32 N = 2e2 + 5, inf = 1e16, P = 1e9;
i32 n, m, mx, u, v, ans = -inf, lim, opt[N], a[N], b[N], in[N], d[N];
i32 f[2][N][N][N];
pii g[N][N * N];
#define cmax(x, y) (x = max(x, y))
fv trans1(i32 flg, i32 u, i32 v) {
i32 a = ::a[v], b = ::b[v], opt = ::opt[v];
if (opt == 0 || opt == 4) {
rep (j, 0, lim) rep (k, 0, lim) cmax(f[flg][v][j][k], f[flg][u][j][k] + a);
} if (opt == 1) {
rep (j, 0, lim) rep (k, 0, lim) cmax(f[flg][v][max((i32)j, a)][k], f[flg][u][j][k] + a * b);
rep (j, 0, lim) rep (k, a, lim) cmax(f[flg][v][max(j, k)][k - a], f[flg][u][j][0] + k * b);
} else if (opt == 2) {
rep (j, 0, lim) rep (k, a, lim) cmax(f[flg][v][j][k - a], f[flg][u][j][k]);
rep (j, 0, lim) rep (k, 0, lim) cmax(f[flg][v][j][k], f[flg][u][j][k]);
} else if (opt == 3) {
rep (j, 0, lim) rep (k, 0, lim) cmax(f[flg][v][j][k], f[flg][u][j][k] + j * a);
}
}
fv trans2(i32 u, i32 v) {
i32 a = ::a[v], b = ::b[v], opt = ::opt[v];
if (opt == 0 || opt == 4) {
rep (j, 0, n * lim) cmax(g[v][j], mp(g[u][j].fi, g[u][j].se + a));
} else if (opt == 1) {
rep (j, 0, n * lim) cmax(g[v][j], mp(g[u][j].fi, g[u][j].se + a * b));
} else if (opt == 2) {
rep (j, 0, n * lim) cmax(g[v][j], max(g[u][j], j >= a ? g[u][j - a] : mp(-inf, -inf)));
} else if (opt == 3) {
rep (j, 0, n * lim) cmax(g[v][j], mp(g[u][j].fi + a, g[u][j].se));
}
}
vi G[N], E[N];
fv tpsort1(i32 flg) {
f[flg][1][0][0] = 0;
rep (u, 1, n) for (i32 v : G[u]) trans1(flg, u, v);
}
fv tpsort2() {
rep (i, 1, n) rep (j, 0, n * lim) g[i][j] = {-inf, -inf};
g[n][0] = {0, 0};
drep (u, n, 1) {
for (i32 v : E[u]) trans2(u, v), cmax(d[v], d[u] + (opt[v] == 4) * a[v]);
}
}
int main() {
cin >> n >> m, mem(f, 0xcf);
rep (i, 1, n) {
cin >> opt[i];
if (opt[i] == 1) cin >> a[i] >> b[i], cmax(lim, a[i]), cmax(lim, b[i]);
else if (opt[i] > 0) cin >> a[i], cmax(lim, a[i] * (opt[i] != 4));
}
rep (i, 1, m) cin >> u >> v, G[u].pb(v), E[v].pb(u);
tpsort1(0), tpsort2();
rep (i, 1, n) {
if (opt[i] == 1) swap(a[i], b[i]);
else if (opt[i] == 2 || opt[i] == 3) opt[i] ^= 1;
}
tpsort1(1);
rep (i, 1, n) if (opt[i] == 1) swap(a[i], b[i]);
rep (i, 1, n) {
if (opt[i] == 1) {
i32 maxn = -inf;
rep (j, 0, lim) rep (flg, 0, 1) cmax(maxn, f[flg][i][j][0]);
maxn -= a[i] * b[i];
rep (j, 0, n * lim) if (g[i][j].fi >= 0) {
cmax(ans, maxn + P * (a[i] + j) * (b[i] + g[i][j].fi) - a[i] * b[i] + g[i][j].se);
}
}
}
cout << max(ans, d[1]) << "\n";
}
CF2061I Kevin and Nivek(*3500)
怎么又是 *3500 dp /ll。
首先考虑一个不算难的 dp,设 \(f_{i,j}\) 代表当前考虑到 \(i\),赢了 \(j\) 场的最小时间。
当 \(a_i=-1\) 枚举 \(j \ge i - 1 - j\) 的 \(j\) 来赢,否则直接选择是否要赢。
这个东西不好优化,我们观察到对于一段区间 \([l,r]\) 的所有 \(a_i=-1\),如果 Kevin 赢的次数大于某个阈值或者小于某个阈值,那么在这一段区间会全赢 / 全输。事实上这两个阈值都可以求出来,如果赢的次数 \(>\dfrac{r}{2}\) 就会全赢,而次数 \(<l-\dfrac{r}{2}\) 就会全输。
所以我们考虑用断点转移加速这个 dp 过程,从 \(f_{l-1}\) 转移到 \(f_r\) 的过程,注意到去除所有 \(-1\) 后,剩下的数肯定是从小往大选,设 \(g_i\) 代表选 \(i\) 个的最小代价,那么这个 \(g\) 肯定是下凸的,所以 \([1,l-\dfrac{r}{2}) \cup [\dfrac{r}{2},r]\) 的部分可以通过决策单调性分治做到 \(O(n \log n)\)。
现在还剩一个长度为 \(r-l\) 的区间未从 \(l\) 转移至 \(r\),不难想到分治,设 \(\text{trans}(l,r)\) 代表从 \(l\) 转移至 \(r\),我们可以把这个长度为 \(r-l\) 的区间下放到儿子节点进行转移,找到中点 \(m\),进行 \(\text{trans}(l,m),\text{trans}(m,r)\) 即可。
using i32 = long long;
const i32 N = 3e5 + 5, inf = 1e16;
i32 n, tag, tot, a[N], cost[N], ans[N];
unordered_map <i32, i32> f[N];
vi S;
fv upd(i32 p, i32 x, i32 v) {
if (!f[p].count(x)) f[p][x] = v;
else f[p][x] = min(f[p][x], v);
}
fv sol(i32 l, i32 r, i32 L, i32 R, i32 p, vector <pii> &v) {
if (l > r) return ;
i32 mid = (l + r) >> 1, pos = L, ans = inf;
rep (i, L, R) {
i32 k = S[mid] - tag - v[i].fi;
if (k < 0 || k > tot) continue;
if (cost[k] + v[i].se < ans) ans = cost[k] + v[i].se, pos = i;
}
upd(p, S[mid], ans);
sol(l, mid - 1, L, pos, p, v), sol(mid + 1, r, pos, R, p, v);
}
fv transfer(i32 l, i32 r) {
if (l + 1 == r) {
if (a[r] == -1) {
for (auto [x, val] : f[l]) {
upd(l + 1, x, val);
if (l - x <= x) upd(l + 1, x + 1, val);
}
}
else {
for (auto [x, val] : f[l]) upd(l + 1, x, val), upd(l + 1, x + 1, val + a[r]);
}
return ;
}
tag = tot = 0;
rep (i, l + 1, r) {
if (a[i] == -1) tag++;
else cost[++tot] = a[i];
}
sort(cost + 1, cost + tot + 1);
rep (i, 1, tot) cost[i] += cost[i - 1];
vector <pii> fL, fM, fR;
for (auto [x, val] : f[l]) {
if (x > r / 2) fR.pb({x, val});
else if (x <= (2 * l - r - 1) / 2) fL.pb({x, val});
else fM.pb({x, val});
}
i32 mid = (l + r) >> 1, cur = -1;
sort(fR.begin(), fR.end()), S.clear();
for (auto [x, val] : fR) {
i32 L = x + tag, R = L + tot;
rep (j, max(L, cur + 1), R) if (j > cur) S.pb(j);
cur = R;
}
if (S.size() && fR.size()) sol(0, S.size() - 1, 0, fR.size() - 1, r, fR);
tag = 0, cur = -1;
sort(fL.begin(), fL.end()), S.clear();
for (auto [x, val] : fL) {
i32 L = x + tag, R = L + tot;
rep (j, max(L, cur + 1), R) if (j > cur) S.pb(j);
cur = R;
}
if (S.size() && fL.size()) sol(0, S.size() - 1, 0, fL.size() - 1, r, fL);
f[l].clear();
for (auto x : fM) f[l].insert(x);
transfer(l, mid), transfer(mid, r);
}
void sol() {
cin >> n;
rep (i, 1, n) cin >> a[i];
rep (i, 1, n + 1) f[i].clear();
f[0][0] = 0;
transfer(0, n);
rep (i, 0, n) ans[i] = f[n][i];
drep (i, n, 1) ans[i - 1] = min(ans[i - 1], ans[i]);
rep (i, 0, n) cout << ans[i] << " ";
cout << "\n";
}
int main() {
IOS;
i32 T; cin >> T;
while (T--) sol();
}

浙公网安备 33010602011771号