51Nod 1487 思维+线段树

题目链接


题意:
有一个 N N N M M M列的网格,每个格子有一个价值。
我们可以选择不多于两个格子建立塔,若塔的坐标为 ( x , y ) (x,y) (x,y)
则我们可以收获 ( x + d x [ 1 ] , y + d y [ 1 ] ) , ( x + d x [ 2 ] , y + d y [ 2 ] ) . . . . ( x + d x [ k ] , y + d y [ k ] ) (x+dx[1],y+dy[1]),(x+dx[2],y+dy[2])....(x+dx[k],y+dy[k]) (x+dx[1],y+dy[1]),(x+dx[2],y+dy[2])....(x+dx[k],y+dy[k])这些格子的价值。
同一个格子的价值只能收获一次。
问能获得的最大值。


思路:
才读完感觉像一个DP,但再仔细一想却感觉无从下手。

主要是处理重叠格子的价值只能取一次这个限制。

然后仔细推敲了一下样例,忽然发现,对于塔在点 ( x , y ) (x,y) (x,y)所能收获的 k k k个格子,点 ( x + 1 , y ) (x+1,y) (x+1,y)所收获的 k k k个格子只不过是上面的 k k k个格子往下移动了一格(向下 x x x轴正向,向右 y y y轴正向)

所以重叠的情况跟 d x [ 1 ] , d y [ 1 ] . . . . d x [ k ] , d y [ k ] dx[1],dy[1]....dx[k],dy[k] dx[1],dy[1]....dx[k],dy[k]的值有关,故考虑 O ( n m ) O(nm) O(nm)枚举每一个点 ( x , y ) (x,y) (x,y),用线段树维护每一个点作为塔时所能收获的价值的最大值。

对于当前枚举的点,枚举其能收获的 k k k个格子,令当前枚举的第 i i i个点坐标为 ( n x , n y ) (nx,ny) (nx,ny)

$nx = x + dx[i] $
n y = y + d y [ i ] ny = y + dy[i] ny=y+dy[i]

假设当某个点 ( t x , t y ) (tx,ty) (tx,ty)作为塔时,该点会被重合,则
n x = t x + d x [ j ] nx = tx + dx[j] nx=tx+dx[j]
n y = t y + d y [ j ] ny = ty + dy[j] ny=ty+dy[j]

枚举 j j j再做减法则可以将 t x , t y tx,ty tx,ty算出,将其值从线段树中减去即可。

最后通过线段树得到区间最大值。
总复杂度: O ( n m k 2 l o g ( n m ) ) O(nmk^2log(nm)) O(nmk2log(nm))

最后加上最优性剪枝,跑出来时间31ms

代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
#define lson rt<<1
#define rson rt<<1|1

const int A = 2e4 + 10;
const int B = 1e2 + 10;
class Seg_Tree{
public:
    int l,r,Mx;
}T[A<<2];
int N,M,K,maze[B][B],dx[B],dy[B];
char s[B];

inline void push_up(int rt){
    T[rt].Mx = max(T[lson].Mx,T[rson].Mx);
}

void build_Tree(int rt,int l,int r){
    T[rt].l = l,T[rt].r = r;
    if(l == r){
        T[rt].Mx = 0;
        return;
    }
    int mid = (l+r)>>1;
    build_Tree(lson,l,mid);
    build_Tree(rson,mid+1,r);
    push_up(rt);
}

void update(int rt,int pos,int val){
    int l = T[rt].l,r = T[rt].r;
    if(l == r){
        T[rt].Mx += val;
        return;
    }
    int mid = (l+r)>>1;
    if(pos<=mid) update(lson,pos,val);
    else         update(rson,pos,val);
    push_up(rt);
}

bool check(int x,int y){
    if(x>=1 && x<=N && y>=1 && y<=M) return true;
    return false;
}

int get_sum(int x,int y){
    int sum = 0;
    for(int i=1 ;i<=K ;i++){
        if(check(x+dx[i],y+dy[i])) sum += maze[x+dx[i]][y+dy[i]];
    }
    return sum;
}

void solve(){
    for(int x=1 ;x<=N ;x++){
        for(int y=1 ;y<=M ;y++){
            update(1,x*M+y,get_sum(x,y));
        }
    }

    int Mx = 0;
    for(int x=1 ;x<=N ;x++){
        for(int y=1 ;y<=M ;y++){
            int res = get_sum(x,y);
            //printf("res = %d",res);
            if(res + T[1].Mx <= Mx) continue;

            for(int i=1 ;i<=K ;i++){
                int n_x = x + dx[i],n_y = y + dy[i];
                if(!check(n_x,n_y)) continue;
                for(int j=1 ;j<=K ;j++){
                    if(check(n_x-dx[j],n_y-dy[j])){
                        update(1,(n_x-dx[j])*M+(n_y-dy[j]),-maze[n_x][n_y]);
                    }
                }
            }

            Mx = max(Mx,res + T[1].Mx);
            //printf("Mx = %d\n",Mx);
            for(int i=1 ;i<=K ;i++){
                int n_x = x + dx[i],n_y = y + dy[i];
                if(!check(n_x,n_y)) continue;
                for(int j=1 ;j<=K ;j++){
                    if(check(n_x-dx[j],n_y-dy[j])){
                        update(1,(n_x-dx[j])*M+(n_y-dy[j]),maze[n_x][n_y]);
                    }
                }
            }
        }
    }
    printf("%d\n",Mx);
}

int main(){
    //freopen("input","r",stdin);
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&N,&M,&K);
        build_Tree(1,M+1,N*M+M);
        for(int i=1 ;i<=N ;i++){
            scanf("%s",s+1);
            for(int j=1 ;j<=M ;j++){
                maze[i][j] = s[j] - '0';
            }
        }
        for(int i=1 ;i<=K; i++) scanf("%d%d",&dx[i],&dy[i]);
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值