题解: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 - 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], '
');
}
原文地址:https://www.cnblogs.com/Ax-Dea/p/15271886.html