2020牛客NOIP赛前集训营-提高组(第一场)D-牛牛的RPG游戏 题解

题目传送门

题目大意: 在一个棋盘上,你要从 ( 1 , 1 ) (1,1) (1,1) 走到 ( n , m ) (n,m) (n,m),只能向右或向下走,每一个格子有一个事件,你可以选择触发或不触发,如果触发你可以得到 v a l i , j val_{i,j} vali,j 的收益,并且得到一个 b u f f i , j buff_{i,j} buffi,j,在这个 b u f f buff buff 的加成下,每走一步会获得 b u f f i , j buff_{i,j} buffi,j 的收益,直到下一次触发事件,身上的 b u f f buff buff 才会改变,求最大收益。

题解

考虑上一个触发事件的位置,容易得到一个方程:
f i , j = max ⁡ { f x , y + b u f f x , y ( i − x + j − y ) + v a l x , y } f_{i,j}=\max\{f_{x,y}+buff_{x,y}(i-x+j-y)+val_{x,y}\} fi,j=max{ fx,y+buffx,y(ix+jy)+valx,y}

拆一下:
f i , j = max ⁡ { b u f f x , y ( i + j ) + f x , y − b u f f x , y ( x + y ) + v a l x , y } f_{i,j}=\max\{buff_{x,y}(i+j)+f_{x,y}-buff_{x,y}(x+y)+val_{x,y}\} fi,j=max{ buffx,y(i+j)+fx,ybuffx,y(x+y)+valx,y}

这变成了一个类似 y = k x + b y=kx+b y=kx+b 的形式,用李超线段树维护出所有 x , y x,y x,y 对应的直线,然后每次询问找横坐标为 i + j i+j i+j 时,纵坐标的最大值即可。

如果 x , y x,y x,y 能对 i , j i,j i,j 产生贡献,那么需要满足:
{ x ≤ i y ≤ j \begin{cases} x\leq i\\ y\leq j \end{cases} { xiyj

这是一个偏序关系,再套一个cdq分治,每层就可以像上面说的那样来做了。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,m;
struct par{
    
    int x,y,id,val,buff;}a[maxn],b[maxn];
struct func{
    
    
	int k,b,l,r;
	int calc(int x){
    
    return k*x+b;}
};
double meet(func x,func y){
    
    return 1.0*(y.b-x.b)/(x.k-y.k);}
#define zuo ch[0]
#define you ch[1]
struct node{
    
    
	int l,r,mid;func z;node *ch[2];
	node(int x,int y):l(x),r(y),mid(l+r>>1){
    
    
		z.l=-1;if(x<y){
    
    
			zuo=new node(l,mid);
			you=new node(mid+1,r);
		}else zuo=you=NULL;
	}
	void clear(){
    
    
		z.l=-1;
		if(zuo&&zuo->z.l!=-1)zuo->clear();
		if(you&&you->z.l!=-1)you->clear();
	}
	void insert(func x){
    
    
		if(x.l<=l&&x.r>=r){
    
    
			if(z.l==-1)z=x;
			else if(x.calc(l)-z.calc(l)>0&&x.calc(r)-z.calc(r)>0)z=x;
			else if(x.calc(l)-z.calc(l)>0||x.calc(r)-z.calc(r)>0){
    
    
				if(x.calc(mid)>z.calc(mid))swap(x,z);
				if(x.k!=z.k)ch[meet(x,z)-mid>1e-8]->insert(x);
			}
		}else{
    
    
			if(x.l<=mid)zuo->insert(x);
			if(x.r>=mid+1)you->insert(x);
		}
	}
	int ask(int x){
    
    
		int re=(z.l!=-1?z.calc(x):0);
		if(l==r)return re;
		return max(re,ch[x>=mid+1]->ask(x));
	}
}*root=NULL;
int f[maxn];
bool cmp(par x,par y){
    
    return x.y<y.y;}
void cdq(int l,int r){
    
    
	if(l==r)return;
	int mid=l+r>>1;cdq(l,mid);
	for(int i=l;i<=r;i++)b[i]=a[i];
	sort(b+l,b+mid+1,cmp);sort(b+mid+1,b+r+1,cmp);
	int x=l,y=mid+1,now=0;
	root->clear();
	while(x<=mid&&y<=r){
    
    
		if(b[x].y<=b[y].y){
    
    
			root->insert((func){
    
    b[x].buff,f[b[x].id]-b[x].buff*(b[x].x+b[x].y)+b[x].val,1,n+m});
			x++;
		}else{
    
    
			f[b[y].id]=max(f[b[y].id],root->ask(b[y].x+b[y].y));
			y++;
		}
	}
	while(y<=r){
    
    
		f[b[y].id]=max(f[b[y].id],root->ask(b[y].x+b[y].y));
		y++;
	}
	cdq(mid+1,r);
}

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			int id=(i-1)*m+j;
			a[id].x=i;a[id].y=j;a[id].id=id;
			scanf("%d",&a[id].buff);
		}
	}
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			scanf("%d",&a[(i-1)*m+j].val);
		}
	}
	root=new node(1,n+m);
	cdq(1,n*m);
	printf("%d",f[n*m]);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109178473