题目大意: 在一个棋盘上,你要从 ( 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(i−x+j−y)+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,y−buffx,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} {
x≤iy≤j
这是一个偏序关系,再套一个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]);
}