CF1131G Most Dangerous Shark

link
单调栈+单调队列+\(dp\)好题。

首先一眼看出是 \(dp\)。令 \(dp[i]\) 为推倒前 \(i\) 个骨牌所耗费的最小价值,(这个并没有单调性。
考虑 \(dp[i]\) 由什么转移来。

1.左边的翻过来(相当于自己向右边翻)。由于能转移过来的 \(j\) 并不是连续的(显然),所以在处理 \(dp[i]\) 时不是很好维护,不妨在处理 \(dp[j]\) 时更新 \(dp[i]\)。不妨设 \(i\) 向左翻能翻倒的最远位置为 \(L[i]\),向右为 \(R[i]\)一个很重要的性质:所有的 \([i,R[i]],[L[i],i]\) 要么相离,要么包含。
\(dp[i]=min(dp[i],dp[j-1]+a[j])(j \le i\le R[j])\)

2.自己向左边翻,则 \(dp[i]=min(dp[i],dp[j-1]+a[i])(L[i]\le j \le i)\)
细心的读者可以发现,第 \(1\) 种情况就是向右翻,所以转移并没有漏掉什么。

你会发现以上所有的东西、所有的转移都可以用线段树/树状数组维护,这里不展开。

关于 \(L,R\) 数组的维护,乍一看用个单调栈/单调队列。发现这题用普通的单调数据结构是没有单调性的,但是为了追求线性复杂度只能抛弃二分。但是这题有一个特殊的性质:若求 \(L[i]\) 时,当前 \(j>=i-h[i]\),那么在单调栈/队列中就可以直接删除(显然),这就保证了线性复杂度,由于 \(j\) 为后进先出,用单调栈即可,求 \(R[i]\) 时同理。

关于第 \(1\) 种情况,单调队列维护即可。注意这里的 \(R[i]\) 虽然没有单调性,但是有局部单调性,即对于相邻的众多区间,他们内部单降(因为所有的 \([i,R[i]]\) 要么相离要么包含)。所以可用单调栈维护。第 \(2\) 种可以稍微偷懒一下,\(dp[i]=dp[L[i]-1]+a[i]\),可以证明是最优的,自己yy一下吧。

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <map>
#include <queue>
#define LL long long
using namespace std;
const int MAXN = 1e7 + 5, MAXM = 250005, inf = 0x3f3f3f3f;
int m, n, k, tot, h[MAXN], que[MAXN], L[MAXN], R[MAXN], head, tail;
int que1[MAXN], head1, tail1;
vector <int> v1[MAXM], v2[MAXM];
LL a[MAXN], dp[MAXN];
void read(int &x) {
	x = 0; int f = 1; char c = getchar();
	for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = 0;
	for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
	x = f ? x : -x;
}
int Min(int x, int y) { return x < y ? x : y; }
int Max(int x, int y) { return x > y ? x : y; }
LL MinLL(LL x, LL y) { return x < y ? x : y; }
LL MaxLL(LL x, LL y) { return x > y ? x : y; }
LL Calc(int x) { return a[x] + dp[x - 1]; }
int main() {
	int x, y;
	read(m); read(n);
	for(int i = 1; i <= m; i ++) {
		read(k);
		for(int j = 1; j <= k; j ++) read(x), v1[i].push_back(x);
		for(int j = 1; j <= k; j ++) read(y), v2[i].push_back(y);
	}
	read(k);
	for(int i = 1; i <= k; i ++) {
		read(x); read(y);
		for(int j = 0; j < v1[x].size(); j ++) h[++ tot] = v1[x][j], a[tot] = (LL)v2[x][j] * y, h[tot] --;//, printf("|%d %lld\n", h[tot], a[tot]);
	}
	head = 1; tail = 0; n = tot;
	for(int i = 1; i <= n; i ++) { // 单调栈 
		L[i] = i;
		while(head <= tail && i - h[i] <= que[tail]) L[i] = Min(L[i], L[que[tail]]), tail --;
		que[++ tail] = i;
	}
	head = 1; tail = 0;
	for(int i = n; i >= 1; i --) {
		R[i] = i; 
		while(head <= tail && i + h[i] >= que[tail]) R[i] = Max(R[i], R[que[tail]]), tail --;
		que[++ tail] = i;
	}
//	for(int i = 1; i <= n; i ++) printf("|%d %d|\n", L[i], R[i]);
	head = 1; tail = 0; dp[0] = 0;
	for(int i = 1; i <= n; i ++) {
		dp[i] = dp[L[i] - 1] + a[i];
		
		while(tail1 && R[que1[tail1]] < i) tail1 --; // case1
		que1[++ tail1] = i;
		while(tail1 > 1 && Calc(que1[tail1]) > Calc(que1[tail1 - 1])) tail1 --;
		if(tail1) dp[i] = MinLL(dp[i], Calc(que1[tail1]));
//		if(tail1) printf("|%d->%d %lld|\n", que1[tail1], i, MinLL(dp[i], Calc(que1[tail1])));
		printf("%lld|", dp[i]);
	}
	printf("%lld", dp[n]);
	return 0;
}	
posted @ 2021-07-12 22:05  Saintex  阅读(47)  评论(0编辑  收藏  举报