题解:CF1131 G.Most Dangerous Shark
首先很容易想到一个 DP 式子和转移,设 \(dp_i\) 表示到前 \(i\) 个骨牌全部被推倒的最小代价。
设 \(l_i\) 表示把这个骨牌向左推倒后,能被推倒的最左边的骨牌的位置,\(r_i\) 表示向右推倒的后,最右边的骨牌的位置。
这个 \(l_i\) 和 \(r_i\) 可以用单调栈或者 [SNOI2017]炸弹 里面的一个 nb \(\mathcal O (n)\) 的算法求出。
转移很好列:
\[dp_i \leftarrow \min_{l_i - 1\le j < i}\{dp_j + cost(i)\}
\]
\[dp_i \leftarrow \min_{r_j \ge i \wedge j < i} \{dp_j + cost(j)\}
\]
这个时候可以用线段树优化一下,就可以做到 \(\mathcal O (n\log n)\) 了。
首先,很显然有 \(dp_i \le dp_j\ (i\le j)\) ,所以第一个转移可以简化为:
\[dp_i \leftarrow dp_{l_i - 1} + cost(i)
\]
然后后面这个转移就比较难搞了,但是发现这样的转移一定是这个样子的:
每一条曲线的左边是 \(i\) ,右边是 \(r_i\) ,然后就可以用单调栈来维护这一个结构了。
总时间复杂度 \(\mathcal O (n)\) 。
typedef long long i64;
const int N = 25e4 + 4, M = 1e7 + 3;
struct node {
int h; i64 c;
node() {}
node(int _h, i64 _c) : h(_h), c(_c) {}
} ;
int n, m, Q, k[N]; basic_string<node> Bs[N], fx;
int l[M], r[M], stk[M], top, h[M], hx[M]; i64 f[M], g[M];
inline void solve() {
Rdn(n, m);
forn(i,1,n) {
Rdn(k[i]); int x;
forn(j,1,k[i]) Rdn(x), Bs[i] += node(x - 1, 0);
forn(j,1,k[i]) Rdn(x), Bs[i][j - 1].c = x;
}
Rdn(Q);
while(Q--) {
int id; i64 mul;
Rdn(id, mul);
int l = fx.size(), r = l + k[id] - 1;
rep(i,0,Bs[id].size()) fx += Bs[id][i];
forn(i,l,r) fx[i].c *= mul, hx[i] = h[i] = fx[i].h;
}
rep(i,1,m) {
l[i] = i;
while (l[i] > 0 && i - l[i] + 1 <= h[i]) {
h[i] = max(h[i], h[l[i] - 1] - (i - l[i] + 1));
l[i] = l[l[i] - 1];
}
}
r[m - 1] = m - 1;
form(i,m - 2,0) {
r[i] = i;
while (r[i] < m - 1 && r[i] + 1 - i <= hx[i]) {
hx[i] = max(hx[i], hx[r[i] + 1] + (r[i] - i + 1));
r[i] = r[r[i] + 1];
}
}
g[top = 0] = 1e18, top = 0;
forn(i,1,m) {
while(top && r[stk[top]] + 1 < i) top -- ;
f[i] = min(g[top], f[l[i - 1]] + fx[i - 1].c);
stk[++top] = i - 1, g[top] = min(g[top - 1], f[i - 1] + fx[i - 1].c);
// forn(j,1,top) Wtn('[', stk[j], ',', g[j], ']', " \n"[j == top]);
}
Wtn(f[m], '\n');
}