P1854-DP【绿】
首先通过这道题我收获了一个知识,那就是deque可以直接赋值,作用和vector类似就是复制一个一摸一样的deque,很好用,越来越发现deque眉清目秀了起来。以后deque可能是我最常用的STL结构了。毕竟queue、stack都用deque来实现明显更方便而且不会多占用什么空间的。
一眼便能看出,这道题用记忆化搜索+动态申请空间应该能大大大大大地减少所用空间,应该还能减少时间,不过大体思路一想便知,而且记搜,本就是自己更熟悉的写法,便也就不写了。
这道题的状态转移不复杂,甚至记录最优解过程这本身也不复杂(由于数据范围过小,不用记搜+空间优化也能过所以才说记录最优解不难,但如果数据范围大一些逼迫我们选择记搜式的存储路径那才算是有些难度了)。这俩者加起来充其量是个黄+的难度,算不上是绿题。但是有一个小小的坑点,那就是非法情况应该排除,这个点不容易想到因此容易导致WA。加上这个点,这道题评个绿还是很公正的。
另外,总结一下排除非法情况的方法,那就是在dp存储最优解的过程中只要把非法情况设置为“可以采取但是效果极其极其差”即可,这样便能让程序发自内心的“可以选择但不选择”。而非直接强迫程序不准选择。这么做好处很明显,明显减少编码复杂度和思维复杂度和出bug的可能性。
补一句,做完题后翻题解看到有人把二维dp数组压成了一维滚动数组,虽然时间复杂度不变但是空间复杂度大大减少,而且基于滚动数组、存储路径的队列组也可以滚动起来,这样就只需要极少的空间占用了,比我之前想的那个记搜+动态空间的方法能减少更多的空间占用而且写起来很简单。光是看到“可以滚动数组”的提议没往下看就瞬间知道该怎么做了,毕竟我的那个状态转移方程明显一看就能写成滚动数组的形式嘛,看来要提高警惕性呀,以后写完状态转移方程后一定要评估一下能否写成滚动数组节约空间,并且分析下空间限制如何有没有必要这么做
#include <iostream>
#include <cstring>
#include <deque>
using namespace std;
int F,V,dp[105][105],a[105][105];
deque<int> q[105][105];
signed main()
{
memset(dp,0,sizeof(dp));
cin>>F>>V;
for(int i=1;i<=F;i++)
for(int j=1;j<=V;j++)
cin>>a[i][j];
for(int i=1;i<=F;i++)dp[i][0]=-99999999;
for(int i=1;i<=F;i++)
{
for(int j=1;j<=V;j++)
{
if(i>j)dp[i][j]=-99999999;
else if(dp[i-1][j-1]+a[i][j]>=dp[i][j-1])
{
dp[i][j]=dp[i-1][j-1]+a[i][j];
q[i][j]=q[i-1][j-1];
q[i][j].push_back(j);
}
else
{
dp[i][j]=dp[i][j-1];
q[i][j]=q[i][j-1];
}
}
}
cout<<dp[F][V]<<endl;
for(auto p=q[F][V].begin();p!=q[F][V].end();p++)
cout<<*p<<' ';
return 0;
}