Jzoj4746 树塔狂想曲
相信大家都在长训班学过树塔问题,题目很简单求最大化一个三角形数塔从上往下走的路径和。走的规则是:(i,j)号点只能走向(i+1,j)或者(i+1,j+1)。如下图是一个数塔,映射到该数塔上行走的规则为:从左上角的点开始,向下走或向右下走直到最底层结束。
1
3 8
2 5 0
1 4 3 8
1 4 2 5 0
路径最大和是1+8+5+4+4 = 22,1+8+5+3+5 = 22或者1+8+0+8+5 = 22。
小S觉得这个问题so easy。于是他提高了点难度,他每次ban掉一个点,然后询问你不走该点的最大路径和。
1
3 8
2 5 0
1 4 3 8
1 4 2 5 0
路径最大和是1+8+5+4+4 = 22,1+8+5+3+5 = 22或者1+8+0+8+5 = 22。
小S觉得这个问题so easy。于是他提高了点难度,他每次ban掉一个点,然后询问你不走该点的最大路径和。
当然他上一个询问被ban掉的点过一个询问会恢复(即每次他在原图的基础上ban掉一个点,而不是永久化的修改)。
n<=1000 m<=50W
显然可以用一个dp做一下,令f[i][j]表示从上到下走到i,j的最大价值,g[i][j]表示从下到上走到i,j的价值
那么有p[i][j]=max(g[i+1][j],g[i+1][j+1])+f[i][j]为经过i,j的最大价值
询问(x,y)的时候,我们只需回答p[x][1..y-1]和p[x][y+1..x]的最大值即可
可用n颗线段树搞定 O(nlgn)
#pragma GCC opitmize("O3")
#pragma G++ opitmize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1010
#define mid (l+r>>1)
using namespace std;
int n,m,c[N][N],f[N][N],g[N][N],w[N][N];
struct seg{
int s[N<<2],*v;
void build(int l,int r,int x){
if(l==r){ s[x]=v[l]; return; }
build(l,mid,x<<1);
build(mid+1,r,x<<1|1);
s[x]=max(s[x<<1],s[x<<1|1]);
}
int query(int l,int r,int x,int L,int R){
if(R<L) return -1;
if(L<=l && r<=R) return s[x];
int k=0;
if(L<=mid) k=max(k,query(l,mid,x<<1,L,R));
if(mid<R) k=max(k,query(mid+1,r,x<<1|1,L,R));
return k;
}
inline void init(int* w){ v=w; build(1,n,1); }
} s[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
scanf("%d",c[i]+j);
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
f[i][j]=max(f[i-1][j],f[i-1][j-1])+c[i][j];
for(int i=n;i;--i)
for(int j=1;j<=i;++j)
g[i][j]=max(g[i+1][j],g[i+1][j+1])+c[i][j];
for(int i=n;i;--i){
for(int j=1;j<=i;++j)
w[i][j]=f[i][j]+max(g[i+1][j+1],g[i+1][j]);
s[i].init(w[i]);
}
for(int x,y;m--;){
scanf("%d%d",&x,&y);
printf("%d\n",max(s[x].query(1,n,1,1,y-1),s[x].query(1,n,1,y+1,x)));
}
}