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掉一个点,然后询问你不走该点的最大路径和。

当然他上一个询问被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
",max(s[x].query(1,n,1,1,y-1),s[x].query(1,n,1,y+1,x)));
	}
}

原文地址:https://www.cnblogs.com/Extended-Ash/p/7887157.html