双线程DP的常识;DP的阶段问题;多阶段决策问题
传送门:$>here<$
题意
n*m的矩阵里,从左上角走到右下角(只能往右或往下),再从右下角走回左上角(只能往左或往上)。其中不能重复经过一个点。问最长路。
数据范围:$n,m \leq 50$
Solution
问题的转化
和上一题有点类似。把往回走的那一条路径看做是从左上到右下的另一条路径。问题转化为求从左上到右下,两条不相交的路径,要求总长最长。
如何转移保证不重复走?
容易想到dp[i][j][a][b]表示第一个人走到(i,j),第二个走到(a,b)的最长路。
于是很快就想到了dp[i][j][a][b]=max{dp[i][j][a][b-1]...}这种转移方法了。这种转移方法的实质是令一个人不动,另一个人走一步。但是很遗憾,这种方法是不能保证不重复的。举一个反例,dp[2][2][1][2]这一个状态,如果从dp[2][2][1][1]转移而来,那么就有可能是(1,1)->(1,2)->(2,2)和(1,1)->(1,2)的叠加。可见(1,2)走了两次。
DP的阶段性
对于这种双线程的DP,往往要两个线程同时考虑。如果同时考虑的话,那么两人某一时刻的位置一定在同一条对角线上(仔细思考)。那么走到同一个点的意思就是两人相遇。那么只要将两人相遇的状态设为无限小,那么就自然避开了。
有了这一点性质(每一对角线作为阶段),DP是可以继续优化的。例如直接用dp[k][i][j]来表示状态。其中k表示第几条对角线,i,j是两人的横坐标。更进一步,由于多阶段DP的某一状态只取决于上一状态,还可以用滚动数组优化。那么就变成二维DP了。
透过题解看本质
双线程与阶段
从这道题里我发现双线程的情况往往同时考虑,并且以每一步决策作为一个阶段。和上一题不同,为什么上一题可以分开转移呢?是因为上一题以往前覆盖一个点作为一个阶段,两人走的步数可以是不一样的。而这道题两人的步数是一样,如果按照不一样考虑就会出现跟随的问题。
多阶段DP问题
将一个DAG分做一个多阶段的DAG往往更清晰。关键是要从题目中看出作为阶段的量。
my code
/*By DennyQi 2019*/ #include <cstdio> #include <queue> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; ; ; const int INF = 0x3f3f3f3f; inline int Max(const int a, const int b){ return (a > b) ? a : b; } inline int Min(const int a, const int b){ return (a < b) ? a : b; } inline int read(){ ; ; register char c = getchar(); '); c = getchar()); , c = getchar(); ) + (x<<) + c - '; return x * w; } int n,m; ][][][],s[][]; int main(){ n = read(), m = read(); ; i <= n; ++i){ ; j <= m; ++j){ s[i][j] = read(); } } memset(dp,-0x3f,sizeof(dp)); dp[][][][] = ; ; i <= n; ++i){ ; j <= m; ++j){ ; a <= n; ++a){ ; b <= m; ++b){ if(i==a && j==b){ continue; } dp[i][j][a][b] = max(max(dp[i-][j][a-][b],dp[i-][j][a][b-]),max(dp[i][j-][a-][b],dp[i][j-][a][b-]))+s[i][j]+s[a][b]; } } } } printf(][m][n][m-]); ; }