首先很容易想到一个 DP 式子和转移,设 (dp_i) 表示到前 (i) 个骨牌全部被推倒的最小代价。
设 (l_i) 表示把这个骨牌向左推倒后,能被推倒的最左边的骨牌的位置,(r_i) 表示向右推倒的后,最右边的骨牌的位置。
这个 (l_i) 和 (r_i) 可以用单调栈或者 [SNOI2017]炸弹 里面的一个 nb (mathcal O (n)) 的算法求出。
转移很好列:
[dp_i leftarrow min_{l_i - 1le j < i}{dp_j + cost(i)}
]
[dp_i leftarrow min_{r_j ge i wedge j < i} {dp_j + cost(j)}
]
这个时候可以用线段树优化一下,就可以做到 (mathcal O (nlog n)) 了。
首先,很显然有 (dp_i le dp_j (ile 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], ']', "
"[j == top]);
}
Wtn(f[m], '
');
}