SDFZOJ#204 RPG游戏

题目描述

一个 $n×m$ 的棋盘,要从左上的 $(1,1)$ 走到右下的 $(n,m)$,只能往右或下走。

走到格子 $(i,j)$ 上时首先会获得一个得分 $val_{i,j}$,然后会面临一个可选的事事件。如果触发,会获得 $buff_{i,j}$ 的持续影响 —— 具体来说,直到触发下一次事件为止,每走一步都会获得 $buff_{i,j}$ 的得分。

求最大的得分。

思路

先考虑一个暴力的 DP:设 $f_{i,j}$ 表示到格子 $(i,j)$ 时的最大得分,容易想到转移方程:

$\large f_{i,j} = \max{f_{k,l}+val_{k,l}+buff_{k,l}×(i-k+j-l)}\ \ \ (1≤k≤i,1≤l≤j,(i,j)≠(k,l))$

把 max 里面的式子的括号里的 $(i,j,k,l)$ 分成两组拆开然后移项:

$\large buff_{k,l}×(i+j)+(f_{k,l}+val_{k,l}+buff_{k,l}×(i+j))$

就变成了一个活脱脱的斜率优化式子!其中 $buff_{k,l}$ 是斜率,$(i+j)$ 是横坐标,剩下的是截距。

注意到横坐标和斜率都不具有单调性,我们可以用李超线段树优化,每个横坐标的答案就是这个位置最高的点。当然也可以 CDQ 分治处理动态凸包。

除了转移的优化,我们还需要注意决策点的处理:「一个位置的答案只能由它之前位置的答案转移过来」,即上面方程中的 $1≤k≤i,1≤l≤j,(i,j)≠(k,l)$。这是一个二维偏序问题,可以用树状数组或者 CDQ 分治处理。

Code

从我个人的角度是比较喜欢 CDQ 分治套李超树的这个写法的,因为它是一个「程序= 数据结构+算法」的结构,有一种艺术和美感在里面。 —— 出题人四糸智乃

由于 potatoler 太菜不会 CDQ 分治,于是献上树状数组套李超线段树的代码。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<climits>
#include<vector>
#define int long long
#define fake false
#define lowbit(x) x&(-x)
using namespace std;
const int MaxN = 100005*15;
int n, m, lim, root[MaxN], cntTree, cntSeg=0;
struct SegmentTree{
	int ls, rs, highest;
}tree[MaxN*4];
struct Segment{
	int k, b;
}segment[MaxN];
vector< vector<int> > val, buff, f;

inline int Read(){
	int num = 0, op = 1;
	char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') op = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		num = num * 10 + ch - '0';
		ch = getchar();
	}
	return num * op;
}

inline int Gety(int serial, int x){
	return segment[serial].k * x + segment[serial].b;
}

inline void Change(int &x, int l, int r, int serial){
	if(!x){
		x = ++cntTree;
		tree[x].highest = serial;
		return;
	}
	// optimize below
	if(Gety(tree[x].highest, l) < Gety(serial, l)){
		if(Gety(tree[x].highest, r) < Gety(serial, r)){
			tree[x].highest = serial;
			return;
		}
	}
	else{
		if(Gety(tree[x].highest, r) > Gety(serial, r)) return;
	}
	// optimize above
	int mid = (l+r) >> 1;
	if(segment[tree[x].highest].k < segment[serial].k){
		if(Gety(tree[x].highest, mid) <= Gety(serial, mid)){
			Change(tree[x].ls, l, mid, tree[x].highest);
			tree[x].highest = serial;
		}
		else Change(tree[x].rs, mid+1, r, serial);
	}
	else if(segment[tree[x].highest].k > segment[serial].k){
		if(Gety(tree[x].highest, mid) <= Gety(serial, mid)){
			Change(tree[x].rs, mid+1, r, tree[x].highest);
			tree[x].highest = serial;
		}
		else Change(tree[x].ls, l, mid, serial);
	}
	else if(segment[tree[x].highest].b < segment[serial].b) tree[x].highest = serial;
	return;
}

inline int Ask(int x, int l, int r, int pos){
	if(!x) return 0;
	int res = Gety(tree[x].highest, pos);
	if(l == r) return res;
	int mid = (l+r) >> 1;
	if(pos <= mid) return max(res, Ask(tree[x].ls, l, mid, pos));
	else return max(res, Ask(tree[x].rs, mid+1, r, pos));
}

inline void Modify(int x, int serial){
	while(x<=m){
		Change(root[x], 1, lim, serial);
		x += lowbit(x);
	}
	return;
}

inline int Query(int x, int pos){
	int res = -LLONG_MAX;
	while(x){
		res = max(res, Ask(root[x], 1, lim, pos));
		x -= lowbit(x);
	}
	return res;
}

signed main(){
	n = Read(), m = Read();
	lim = n + m;
	val.resize(n+1);
	buff.resize(n+1);
	f.resize(n+1);
	for(int i=1; i<=n; i++){
		val[i].resize(m+1);
		buff[i].resize(m+1);
		f[i].resize(m+1);
	}
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)
			buff[i][j] = Read();
	for(int i=1; i<=n; i++)
		for(int j=1; j<=m; j++)
			val[i][j] = Read();
	segment[0] = (Segment){0,0};
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			if(i == 1 && j == 1){
				Modify(1, 0);
				continue;
			}
			cntSeg++;
			f[i][j] = Query(j, i+j);
			segment[cntSeg].k = buff[i][j];
			segment[cntSeg].b = f[i][j] + val[i][j] - buff[i][j] * (i+j);
			Modify(j, cntSeg);
		}
	}
	printf("%lld", f[n][m]);
	return 0;
}
Sorry potatoler, but I cannot recognize you.
Built with Hugo
Theme Stack designed by Jimmy