题解: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');
}
posted @ 2021-09-15 13:45  AxDea  阅读(29)  评论(0编辑  收藏  举报