P1004 [NOIP2000 提高组] 方格取数
按照惯例,先来一遍题目~
设有 N×N 的方格图 (N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0。
某人从图的左上角的 A点出发,可以向下行走,也可以向右走,直到到达右下角的 B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
此人从 A 点到 B 点共走两次,试找出 2 条这样的路径,使得取得的数之和为最大。
输入格式
输入的第一行为一个整数 N(表示 N×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 表示输入结束。
输出格式
只需输出一个整数,表示 2 条路径上取得的最大的和。
思路奉上
很明显,这是一道动态规划的题目~
(在洛谷上做第一道动态规划题目的时候,我还很是头疼,想不清楚那个方程,今天可是顺利多了,看来还是要多多练习的哈)
不啰嗦了,先讲思路:
题目的意思呢,就是要找两条路,取了路上的数字,使两条路得到的和最大,一遇到这样的问题,动态规划
必须安排~
两条路如果不好想的话,我们先来想一条路毕竟这样简单的多
假设我们要找一条路来走,使途经得到的数字和最大,要怎么办呢?
因为我们只有向下和向右两种走法,这就和前两天做的一道题目类似了,它在这里 呃,仅做参考,只是我觉得很类似
方程也就是这样:
num[i][j]=max(num[i-1][j]+a[i][j],num[i][j-1]+a[i][j])
思路很简单,就是看在这一步的基础上看前一步是走的哪里(当然的使总值大的那一个)
好了,一条路走很简单,只看一看动态规划方程就好了,我们重点来看两条路,小女子不才,开始的时候选择了一个很笨的方法:用两个二维数组来分别记录两条路,于是就有了这样的方程:大家不要看,这是错误示范,我只是想记录一下~
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(i==0&&j==0)
{
num[i][j]=arr[i][j];
mum[i][j]=arr[i][j];
}
else if(i==0&&j>0)
{
num[i][j]=num[i][j-1]+arr[i][j];
mum[i][j]=mum[0][0];
}
else if(j==0&&i>0)
{
mum[i][j]=mum[i-1][j]+arr[i][j];
num[i][j]=num[0][0];
}
else
{
p=max(num[i-1][j]+arr[i][j],num[i][j-1]+arr[i][j]);
q=max(mum[i-1][j]+arr[i][j],mum[i][j-1]+arr[i][j]);
if(p==num[i-1][j]+arr[i][j]&&q==mum[i-1][j]+arr[i][j])
{
if(p>=q)
{
num[i][j]=p;
mum[i][j]=mum[i][j-1]+arr[i][j];
}
else
{
mum[i][j]=q;
num[i][j]=num[i][j-1]+arr[i][j];
}
}
else if(p==num[i][j-1]+arr[i][j]&&q==mum[i][j-1]+arr[i][j])
{
if(p>=q)
{
num[i][j]=p;
mum[i][j]=mum[i-1][j]+arr[i][j];
}
else
{
mum[i][j]=q;
num[i][j]=num[i-1][j]+arr[i][j];
}
}
else{
num[i][j]=p;
mum[i][j]=q;
}
}
}
}
很麻烦,而且还不对,所以我们来看正确的思路~
我的错误在于两个二维数组
用两个数组怎么可能好过一个呢,我们直接用一个四维数组它不香嘛!
没错,就是这样:当解决这样的问题时,放在二维空间内通常是不好想的,所以我们要引入高维空间来解题~
我们定义一个四维数组:
int num[n+1][n+1][n+1][n+1];//n+1只是为了不用0
那么num[i][j][p][q]
代表什么意思呢?
就是一条路当前走了a[i][j]
这个点,另一条路走了a[p][q]
这个点,要注意的就是如果这两个点相等怎么办呢?(我们稍后再谈~)
先来看动态规划方程要怎么写~
(1)我们要知道当前有四个状态,他们分别是:
num[i-1][j][p][q-1]
num[i-1][j][p-1][q]
num[i][j-1][p][q-1]
num[i][j-1][p-1][q]
类比于二维空间(一条路)的两个状态:
num[i-1][j]+arr[i][j]
num[i][j-1]+arr[i][j]
我们要做的是什么呢?就是找出我们当前步的上一步走谁这四个状态谁是最优的(也就是取值最大的)
那么,方程就出来了,当当当当~
num[i][j][p][q]=arr[i][j]+arr[p][q]+max(num[i-1][j][p][q-1],max(num[i-1][j][p-1][q],max(num[i][j-1][p][q-1],num[i][j-1][p-1][q])));
方程有了之后就是小细节了,我们遗留的那个问题:如果走了同一个点怎么办?
因为点可以走两次,但数只可以取一次问:为什么?答:看题目
所以我们直接减一次就好了,此时a[rri][j]==arr[p][q]
if(i==p&&j==q)
num[i][j][p][q]-=arr[i][j];
好了,写到这里,就很明了了呢,我们直接看完整代码,当当~
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
int arr[n+1][n+1];
int p,q,i,j;
int num[n+1][n+1][n+1][n+1];
memset(arr,0,sizeof(arr));
memset(num,0,sizeof(num));
int a,b,c;
while(1)
{
cin>>a>>b>>c;
if(a==0&&b==0&&c==0)
break;
else{
arr[a][b]=c;
}
}
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
for(p=1;p<=n;p++)
{
for(q=1;q<=n;q++)
{
//四种情况:
//num[i-1][j][p][q-1]
//num[i-1][j][p-1][q]
//num[i][j-1][p][q-1]
//num[i][j-1][p-1][q]
//取max
num[i][j][p][q]=max(num[i-1][j][p][q-1],max(num[i-1][j][p-1][q],max(num[i][j-1][p][q-1],num[i][j-1][p-1][q])))+arr[i][j]+arr[p][q];
if(i==p&&j==q)
num[i][j][p][q]-=arr[i][j];
}
}
}
}
cout<<num[n][n][n][n]<<endl;
return 0;
}
结束,做一个快乐的程序猿~