步步为零

【问题描述】 
 
  一个特殊的数字阵列,共有2*N-1行,每个方格中有一个数码Ai(-50<=Ai<=50),例如N=4的情况。
                    
  HB从最下面方格出发,每次可以跳到上一行与自己所在格子相邻的其中一个方格内(例如在最下面的7中,可以跳到上一行的10和8中),他每到达一个方格,就将该方格的数记录下来。当他到达最顶格子的时候,拿出记录数字,可以在任意两个数字间添加“+”或“-”号,使得计算的结果m最接近0。现在请你帮助他。

【输入格式】

 
  第一行是N,接下来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
    
  【数据范围】 
 
  1<=N<=30

 

 

 

 

【来源】

 

 


  最优解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


 

 

 

 

 

 

 

题目类型:动态规划

这是一道难题!

根据题目描述,我们知道,每个数字在-50~50之间,一共最多有2*30-1=59行,所以走完全部取得的数字结果范围一定在-50*59~50*59之间,即在-2950~2950之间。我们发现,这个数字并不大,由此我们可以得到方程f(i,j,k)表示走到(i,j)时取得数经过运算能否得到k,若能,则值为1,否则为0

所以状态转移方程为     f(i , j , k)={f(i+1 , j , k+a[i][j]) || f(i+1 , j , k-a[i][j]) || f(i+1 , j+1 , k+a[i][j]) || f(i+1 , j+1 , k-a[i][j])}   (i<=N) 

                                           f(i , j , k)={f(i+1 , j , k+a[i][j]) || f(i+1 , j , k-a[i][j]) || f(i+1 , j-1 , k+a[i][j]) || f(i+1 , j-1 , k-a[i][j])}   (N<i<=2*N-1)   (这种情况要单独讨论j=1 和 j=2*N-1的情况)

程序从第2*N-2层开始往上搜。所以边界条件为 f(2*N-1 , 1 , a[2*N-1][1])=1

最后的答案就是从i=0开始第一个f(1 , 1 , i)=1 或者 f(1 , 1 , -i)=1的i值即是答案

关于负数下标应该用宏来处理更为简单!

 

程序

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>


#define f(i,j,k) d[(i)][(j)][(k)+2000]     //定义宏


using namespace std;

int N;
int a[70][50];


int d[70][50][5500];

int tot=0;

 

void Ready()
{
 scanf("%d",&N);
 for(int i=1;i<=N;i++)
  for(int j=1;j<=i;j++)
  {
   scanf("%d",&a[i][j]);
   tot+=abs(a[i][j]);
  }
 for(int i=1;i<N;i++)
  for(int j=1;j<=N-i;j++)
   {
    scanf("%d",&a[i+N][j]);
    tot+=abs(a[i+N][j]);
   }
   memset(d,0,sizeof(d));
}

 

void Working()
{
 f(2*N-1,1,a[2*N-1][1])=1;
 for(int i=2*N-2;i>=N;i--)
  for(int j=1;j<=2*N-i;j++)
   for(int k=-tot;k<=tot;k++)
   {
    if(j>1 && j<2*N-i)
    {
    if(f(i+1,j-1,k-a[i][j]) || f(i+1,j-1,k+a[i][j]) || f(i+1,j,k-a[i][j]) || f(i+1,j,k+a[i][j]))f(i,j,k)=1;
    }
  if(j==1)
   {
     if(f(i+1,j,k-a[i][j]) || f(i+1,j,k+a[i][j]))f(i,j,k)=1; 
   }
   if(j==2*N-1)
   {
      if(f(i+1,j-1,k-a[i][j]) || f(i+1,j-1,k+a[i][j]))f(i,j,k)=1;
   }
   }
 for(int i=N-1;i>=1;i--)
  for(int j=1;j<=i;j++)
   for(int k=-tot;k<=tot;k++)
   {
    if(f(i+1,j+1,k-a[i][j]) || f(i+1,j+1,k+a[i][j]) || f(i+1,j,k-a[i][j]) || f(i+1,j,k+a[i][j]))f(i,j,k)=1;
 }
 int ans;
 for(int i=0;i<=tot;i++)
  {
   if(f(1,1,i) || f(1,1,-i))
   {
    ans=i;
    break;
   }
  }
  printf("%d",ans);
   
}

int main()
{
 Ready();
 Working();
 return 0;
}

 


 

by:重庆一中吴松隐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值