题目链接:http://acm.dlut.edu.cn/problem.php?id=1268
题目描述:
给出一个数字构成的矩阵,小明可以把第一排的任一点作为起点,把地图中的任意一点作为终点进行移动,移动方式仅限向左一步,向右一步或向下一步,也就是说不能往上走,小明每路过一个点,就会捡起这个点上的数字加到自己的得分里,每个点的分数只能被捡起一次,也就是说捡完之后改点变成0,问小明最大得分。
解题思路:
一道DP,看似背包,不是背包,是道普通而纠结的DP,想法不难,但以我目前的水平在比赛的真实环境下真的做不出。
如何DP。。听我慢慢道来
我讲的可能没按解题的思考顺序来...表达能力比较差~谅解哈
首先首先需要明白这样一件事,在同一行里,从 i --> j 得到的分数与从 j --> i 得到的分数与 i --> j --> i 得到的分数都是一样的。——结论①
于是我们可以产生这样一种想法,只考虑一行里的情况——我们也许能求出把某个点作为终点能得到的最大分数。
起点是第一排,那我们就先考虑一下第一排的事。
根据经验我们不难想到这样两个dp数组,ldp和rdp。
ldp【i】是从左侧某点起连续到 i 点能得到的最大分数,根据结论①也可以理解成是,以这点 i 为起点只向左走能得到的最大值。
ldp【i】= max( ldp【i -1】+ arr【i】,arr【i】)
同理,rdp就是换成右边的,差不多的东西。
下面举个栗子
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
ldp | 1 | 2 | 3 | -2 | 1 | 2 | 3 |
arr | 1 | 1 | 1 | -5 | 1 | 1 | 1 |
rdp | 3 | 2 | 1 | -2 | 3 | 2 | 1 |
temp | 3 | 3 | 3 | 1 | 3 | 3 | 3 |
求法如下:
temp【i】= max(arr【i】,arr【i】+ ldp【i-1】,arr【i】+rdp【i+1】,arr【i】+ ldp【i-1】+ rdp【i+1】) //证明略
但一定要注意!这是只考虑这一行的时候这么干,我们只对第一行的最终dp值dp【i】【j】(dp【i】【j】是以【i】【j】为终点能得到的最大分数)是这么求出来的。而求其他行的时候,因为其他行是从上一行搞下来的,所以上一行的dp值会对这一行产生影响,只考虑这一行的情况是行不通的,我们要遍历一遍求最优解。
怎么求第一行以外行的dp值呢
在求dp【i】【j】的时候设一个掉落点k,表示是从【i-1】【k】位置掉落到第 i 行的,随后移动到【i】【j】点位置。
以 k 在 j 左侧为例,如下图所示:
显然为了让dp【i】【j】最大,dp【i】【j】= ldp【k】+num【i】【k+1】+...+ num【i】【j-1】+rdp【j】
k = j 和 k > j 的情况另外写一下,大同小异。
如此遍历所有的 k 就可以求出dp【i】【j】的最大值。
求数组里区间和只要预处理一下就可以把时间复杂度降到o(1)。所以按照以上算法的复杂度是o(n*m*m),按照题目数据范围,最大是1000*50*50,给了足足5s,肯定能过。而比赛的时候正是因为一直在想o(n*m)的才把自己作死。
反思总结:
你说。。这题难吗。也不算很难,图书馆里静静地审视一下,看一下数据范围,就过了,比赛时候就没过。确实,这道dp的求解方式比较纠结,折腾了好几遍,还是有一定难度的。不过比赛时候我们没有看清题目也没有完全验证自己dp公式的对错就开始写了,这是个败笔,写dp最恐怖的是公式本身严重错误,却一直没发现,但凡能用dp解决的问题,其正确公式一定是显而易见一想就绝对对的,而不可能是想起来好像对,真正证明又模棱两可的!我觉得这道题出的非常好,很考验代码能力和比赛素质,比赛没做出来是水平不够,唉~这点认输。真正看来,并不是很难的题,说是中等题恐怕都要被大神嘲笑,完全有能力做出来,通过这题也算是增长了水平,以后再遇到这种“水题”,一定要做出来!
AC代码:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
int N,M;
ll num[1200][120];
ll sum[1200][120];
ll temp[1200][120];
ll ldp[120];
ll rdp[120];
ll MMax(ll a,ll b,ll c,ll d)
{
ll x=max(a,b);
ll y=max(c,d);
return x>y?x:y;
}
int main()
{
//freopen("input.txt","r",stdin);
int T,i,j,k;
cin>>T;
while(T--)
{
memset(num,0,sizeof(num));
memset(temp,0,sizeof(temp));
cin>>N>>M;
for(i=0;i<N;i++)
for(j=0;j<M;j++)
{
cin>>num[i][j];
if(j==0)
sum[i][0]=num[i][0];
else
sum[i][j]=num[i][j]+sum[i][j-1];
}
for(i=0;i<N;i++)
{
memset(ldp,0,sizeof(ldp));
memset(rdp,0,sizeof(rdp));
ldp[0]=num[i][0];
rdp[M-1]=num[i][M-1];
for(j=1;j<M;j++)
ldp[j]=max(ldp[j-1]+num[i][j],num[i][j]);
for(j=M-2;j>=0;j--)
rdp[j]=max(rdp[j+1]+num[i][j],num[i][j]);
if(i==0)
{
temp[i][0]=max(num[i][0]+rdp[1],num[i][0]);
if(M>=2)
temp[i][M-1]=max(num[i][M-1]+ldp[M-2],num[i][M-1]);
for(j=1;j<M-1;j++)
temp[i][j]=MMax(ldp[j-1]+num[i][j],num[i][j]+rdp[j+1],ldp[j-1]+num[i][j]+rdp[j+1],num[i][j]);
}
else
{
for(j=0;j<M;j++)
{
ll maxtemp;
if(j==0)
maxtemp=max(temp[i-1][j]+num[i][j],temp[i-1][j]+num[i][j]+rdp[j+1]);
else
maxtemp=MMax(temp[i-1][j]+num[i][j],temp[i-1][j]+num[i][j]+ldp[j-1],temp[i-1][j]+num[i][j]+rdp[j+1],temp[i-1][j]+num[i][j]+ldp[j-1]+rdp[j+1]);
for(k=0;k<j;k++)
maxtemp=max(temp[i-1][k]+ldp[k]+sum[i][j-1]-sum[i][k]+rdp[j],maxtemp);
for(k=j+1;k<M;k++)
maxtemp=max(temp[i-1][k]+ldp[j]+sum[i][k-1]-sum[i][j]+rdp[k],maxtemp);
temp[i][j]=maxtemp;
}
}
}
/*
for(i=0;i<N;i++)
{
for(j=0;j<M;j++)
cout<<temp[i][j]<<" ";
cout<<endl;
}
*/
ll ans=temp[0][0];
for(i=0;i<N;i++)
for(j=0;j<M;j++)
ans=max(ans,temp[i][j]);
cout<<ans<<endl;
//cout<<endl;
}
return 0;
}
AC截图:
//。。。。才跑了472ms = =