[BZOJ1510]遇见

题目描述

燕姿在桥的这一端,而xx在桥的另一端。这座桥非常特殊,桥面是由2N-1个方格组成的,每个方格里写有一个数码Ai(-50<=Ai<=50)。如下是N=4时的情况。可以认为燕姿从最下面出发。每一次,她可以向上跳到与自己所在方格相临的其中一个方格内(例如在最下面的7中,可以跳到上一行的10和8中)。当燕姿跳到最顶端的方格后,她就不能再移动了。(在未到顶端前,不允许跳到表格外。)每在一格内,都要把格内的数字写下来。 但是,仅仅到达顶端是不够的。桥会向对岸的xx询问一个数字k,燕姿到达顶端后,拿出写下来的数字,可以在任意两个数字间添加“+”或“-”号,使得计算的结果m最接近k。经过桥的判断,如果对于桥上的方格m是最接近k的数字,那么燕姿就可以通过桥和xx相遇,否则……… (为了让燕姿能更容易地通过,xx给出的数字总是0)你的任务,就是帮助燕姿找出这个最接近k的m.


输入

输入的第一行是N(1<=N<=30),接下来2N-1行给出了表格中每行的每个方格中的数字,第i+1行的第j个数字对应于表格中第i行的第j个数字。文件中第二行的数字表示的是表格顶端的方格中的数字。所有的数字都是整数,同一行相邻的两个数字间用空格符隔开。


输出
输出只有一行,是你所求出的最接近零的计算结果的绝对值

样例输入:
4                                    
2
3 1
-3 5 7
6 10 -2 20
-7 -5 -8
10 8 
7

样例输出:
0

提示:
最优解7+8+(-5)+(-2)-5-1-2=0
或7+10+(-7)-6+(-3)-3+2=0

或7+10+(-5)-10-5+1+2=0

或+10+(-5)+(-2)-5-3-2=0

题解:

设num为桥上某一行某一列的数字 

dp[i][j][k]:第i行第j列所有数字是否可行

dp[i][j][k]=f[i-1][j-1][ k-num ] || f[i-1][j-1][ k+num ] || f[i-1][j][ k-num ] || dp[i-1][j][ k+num ]

但是f数组过于巨大肯定是开不下, 
递推的一维最多用到i和i-1, 
于是可以使用滚动数组节约空间

dp[i&1][j][k]=f[!(i&1)][j-1][ k-num ] || f[!(i&1)][j-1][ k+num ] || f[!(i&1)][j][ k-num ] || dp[!(i&1)][j][ k+num ]

#include<iostream> 
#include<cstring> 
#include<cstdio> 
#include<cstdlib> 
#include<cmath> 
#include<algorithm> 
using namespace std; 
const int N=50; 
const int M=3000; 
const int Mid=M>>1; 
int n, num, ans, u, d; 
bool dp[2][N][M]; 
  
int main() { 
    scanf( "%d%d", &n, &num ); 
    dp[1][1][ Mid-num ]=1; //数组的下标不能为负数, 就将1500作为0分界线, 1500以下为负数, 
    dp[1][1][ Mid+num ]=1;//至于为什么用1500, 因为一层的最大和为30*50=1500
    for( int i=2; i<=n; i++ ) { 
        memset( dp[i&1], 0, sizeof dp[i&1] );
        for( int j=1; j<=i; j++ ) { 
            scanf( "%d", &num ); 
            for( int k=N; k<=M-N; k++ )//num最大为50
                if( dp[!(i&1)][j-1][k+num] || dp[!(i&1)][j-1][k-num] || dp[!(i&1)][j][k+num] || dp[!(i&1)][j][k-num] ) 
                    dp[i&1][j][k]=1; 
        } 
    } 
    for( int i=n-1; i>=1; i-- ) { 
        memset( dp[i&1], 0, sizeof dp[i&1] ); 
        for( int j=1; j<=i; j++ ) { 
            scanf( "%d", &num ); 
            for( int k=0; k<N; k++ ) //num最大为50
                if( dp[!(i&1)][j][k+num] || dp[!(i&1)][j+1][k+num] ) 
                    dp[i&1][j][k]=1; 
            for( int k=N; k<=M; k++ ) 
                if( dp[!(i&1)][j][k+num] || dp[!(i&1)][j][k-num] || dp[!(i&1)][j+1][k+num] || dp[!(i&1)][j+1][k-num] ) 
                    dp[i&1][j][k]=1; 
        } 
    } 
    for( u=Mid; u<=M; u++ ) if( dp[1][1][u] ) break; 
    for( d=Mid; d>=0; d-- ) if( dp[1][1][d] ) break; 
    ans=min( u-Mid, Mid-d );
    printf("%d\n",ans); 
    return 0; 
}


f(i,j,k)=f(i1,j1,kinp[i][j])||f(i1,j1,k+inp[i][j])||f(i1,j,kinp[i][j])||f(i1,j,k+inp[i][j]的递但是f数
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值