NOIP提高组 树塔狂想曲

17 篇文章 0 订阅

Description

相信大家都在长训班学过树塔问题,题目很简单求最大化一个三角形数塔从上往下走的路径和。走的规则是:(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掉一个点,而不是永久化的修改)。

Data Constraint

这里写图片描述

Solution

这道题考场上有点弱智,没去打dp,反而去打spfa(手残……)。但不管怎样,思路是一致的。

我们从第一层正着做一遍spfa,得出第一层到每个点的最大值。然后再倒着做一遍,得出每个点到第n层的最大值。显然,对于一次删点,假如没删在最长路上对答案是没有影响的。而且,我们设第一层到点i的最大路径为x,点i到第n层的最大值为y,点i的值为z,那么从第一层到第n层且经过i点的最大值为x+y-z(证明显然)。那么我们只要找到对答案有影响的删除点所处的那一层除这个点以外的最大值即可。这些东西预处理一下就好了。询问O(1)。

代码

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int f1[2][2]={{1,0},{1,1}};
const int g[2][2]={{-1,0},{-1,-1}};
const int maxn=1005;
int a[maxn][maxn],d[maxn*maxn],f[maxn*maxn],fa[maxn*maxn],v[maxn*maxn*4];
int x,y,n,m,i,t,j,k,l,p,ans,b[maxn*10],d1[maxn*maxn],ans1[maxn];
bool bz[maxn*maxn],bz1[maxn*maxn];
int main(){
//  freopen("data.in","r",stdin);
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++)
        for (j=1;j<=i;j++)
            scanf("%d",&a[i][j]);
    v[1]=1;i=0;j=1;d[1]=a[1][1];bz[1]=true;
    while (i<j){
        i++;
        if (v[i]/n+1>=n) break;
        for (k=0;k<=1;k++){
            x=v[i]/n+1+f1[k][0],y=v[i]%n+f1[k][1];
            if (d[(x-1)*n+y]<d[v[i]]+a[x][y]){
                d[(x-1)*n+y]=d[v[i]]+a[x][y];
                fa[(x-1)*n+y]=v[i];
                if (!bz[(x-1)*n+y]){
                    v[++j]=(x-1)*n+y;
                    bz[(x-1)*n+y]=true;
                }
            }
        }
        bz[v[i]]=false;
    }
    memset(bz,0,sizeof(bz));
    for (i=1;i<=n;i++)
        if (d[(n-1)*n+i]>k){
            k=d[(n-1)*n+i];
            p=(n-1)*n+i;
        }ans=k; 
    while (p) b[++b[0]]=p,bz1[p]=true,p=fa[p];
    for (i=1;i<=n;i++)
        v[i]=(n-1)*n+i,d1[(n-1)*n+i]=a[n][i],bz[(n-1)*n+i]=true;
    i=0;j=n;
    while (i<j){
        i++;
        if (v[i]==1) break;
        for (k=0;k<=1;k++){
            x=v[i]/n+1+g[k][0],y=v[i]%n+g[k][1];
            if (!(v[i]%n)) x--,y+=n;
            if (!y) continue;
            if (d1[(x-1)*n+y]<d1[v[i]]+a[x][y]){
                d1[(x-1)*n+y]=d1[v[i]]+a[x][y];
                if (!bz[(x-1)*n+y]){
                    v[++j]=(x-1)*n+y;
                    bz[(x-1)*n+y]=true;
                }
            }
        }
        bz[v[i]]=false;
    }
    for (i=n;i>=2;i--){
        k=0;
        for (j=1;j<=i;j++){
            if ((i-1)*n+j==b[n-i+1]) continue;
            k=max(k,d[(i-1)*n+j]+d1[(i-1)*n+j]-a[i][j]);
        }
        ans1[n-i+1]=k;
    }
    for (i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        if (x==1) printf("-1\n");
        else if (bz1[(x-1)*n+y]) printf("%d\n",ans1[n-x+1]);
        else printf("%d\n",ans);
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值