线性DP
DP三要素:状态,阶段,决策(转移)
阶段:第i层
状态:目前情况
写代码三要素:边界、目标、转移
DP要求:无后效性
Mr. Young's Picture Permutations
link
要求从左到右和从上到下都递减
首先肯定按顺序加入
从左到右很明确,加到最右边
从上到下怎么维护? 其实就是这一行加完之后不超过上一行就行
发现行数很少,直接变成状态
\(dp[b_1][b_2][b_3][b_4][b_5]\)表示第\(i\)行用了\(b_i\)个位置的方案
初始:\(dp[0][0][0][0][0]=1\)
转移:首先要不超范围,\(b_i<a_i\)
- 第\(1\)行随便加
- 对于第\(i\)行\((i\ne1)\) 要求\(b_i<b_{i-1}\)即可
开始感觉直接枚举是不是会漏因为阶段不是那么好处理,就写了bfs求dag
结果看了一下题解其实是可以直接枚举的,因为对于\(dp[b_1][b_2][b_3][b_4][b_5]\)
他的所有贡献来源其实都是有一维比他小的,在他被枚举到准备更新别人时,其实他已经是求完了
其他两个错误点:
1.unsigned int 直接int不能过 可以用 ll 但注意把所有涉及方案数的int改成ll!!(本题的x)
2. 直接开数组会MLE 因为下面的长度肯定小于上面的,所以第\(i\)行最多\(30/i\)个,节省空间
放一下bfs代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
using namespace std ;
typedef long long ll ;
const int N = 1000010 ;
struct node {
int b1, b2, b3, b4, b5 ;
};
int n ;
int a[10] ;
ll dp[31][31 / 2 + 1][31 / 3 + 1][31 / 4 + 1][31 / 5 + 1] ;
int vis[31][31 / 2 + 1][31 / 3 + 1][31 / 4 + 1][31 / 5 + 1] ;
queue <node> q ;
void init() {
memset(dp, 0, sizeof(dp)) ;
memset(vis, 0, sizeof(vis)) ;
memset(a, 0, sizeof(a)) ;
}
int main() {
while (scanf("%d", &n) != EOF && n) {
init() ;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]) ;
dp[0][0][0][0][0] = 1 ; vis[0][0][0][0][0] = 1 ;
q.push((node){0, 0, 0, 0, 0}) ;
while (!q.empty()) {
node t = q.front() ; q.pop() ;
int b1 = t.b1, b2 = t.b2, b3 = t.b3, b4 = t.b4, b5 = t.b5 ;
ll x = dp[b1][b2][b3][b4][b5] ;
if (b1 < a[1]) {
dp[b1 + 1][b2][b3][b4][b5] += x ;
if (!vis[b1 + 1][b2][b3][b4][b5]) {
q.push((node){b1 + 1, b2, b3, b4, b5}) ;
vis[b1 + 1][b2][b3][b4][b5] = 1 ;
}
}
if (b2 < a[2] && b2 < b1) {
dp[b1][b2 + 1][b3][b4][b5] += x ;
if (!vis[b1][b2 + 1][b3][b4][b5]) {
q.push((node){b1, b2 + 1, b3, b4, b5}) ;
vis[b1][b2 + 1][b3][b4][b5] = 1 ;
}
}
if (b3 < a[3] && b3 < b2) {
dp[b1][b2][b3 + 1][b4][b5] += x ;
if (!vis[b1][b2][b3 + 1][b4][b5]) {
q.push((node){b1, b2, b3 + 1, b4, b5}) ;
vis[b1][b2][b3 + 1][b4][b5] = 1 ;
}
}
if (b4 < a[4] && b4 < b3) {
dp[b1][b2][b3][b4 + 1][b5] += x ;
if (!vis[b1][b2][b3][b4 + 1][b5]) {
q.push((node){b1, b2, b3, b4 + 1, b5}) ;
vis[b1][b2][b3][b4 + 1][b5] = 1 ;
}
}
if (b5 < a[5] && b5 < b4) {
dp[b1][b2][b3][b4][b5 + 1] += x ;
if (!vis[b1][b2][b3][b4][b5 + 1]) {
q.push((node){b1, b2, b3, b4, b5 + 1}) ;
vis[b1][b2][b3][b4][b5 + 1] = 1 ;
}
}
}
printf("%lld\n", dp[a[1]][a[2]][a[3]][a[4]][a[5]]) ;
}
}
Mobile Service
link
这个题涉及到一类减少空间复杂度的方法
初步的想法肯定是\(dp[i][x][y][z]\)表示搞到第\(i\)个任务,三个人在\(x,y,z\)的最小成本
但发现肯定有一个人在\(p[i-1]\)
于是去掉一位,枚举选哪个人过去转移即可
这个题很坑,要求三个人不能到一起去,要把所有的条件全部写清楚才能AC(WA了很多遍)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std ;
typedef long long ll ;
const int N = 310 ;
const int M = 3010 ;
const int inf = 0x3f3f3f3f ;
int T, n, m ;
int a[N][N] ;
int q[M] ;
int dp[M][N][N] ;
int main() {
scanf("%d", &T) ;
while (T--) {
scanf("%d%d", &n, &m) ;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%d", &a[i][j]) ;
for (int i = 1; i <= m; i++) scanf("%d", &q[i]) ;
memset(dp, 0x3f, sizeof(dp)) ;
dp[0][1][2] = 0 ; q[0] = 3 ;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
if (j != k && j != q[i - 1] && k != q[i - 1] && dp[i - 1][j][k] != inf) { //##
if (j != q[i] && q[i] != k)
dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j][k] + a[q[i - 1]][q[i]]) ;
// use q[i-1]
if (q[i - 1] != k && k != q[i])
dp[i][q[i - 1]][k] = min(dp[i][q[i - 1]][k], dp[i - 1][j][k] + a[j][q[i]]) ;
// use j
if (q[i - 1] != j && j != q[i])
dp[i][j][q[i - 1]] = min(dp[i][j][q[i - 1]], dp[i - 1][j][k] + a[k][q[i]]) ;
// use k
}
int ans = inf ;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i != j && j != q[m] && i != q[m])
ans = min(ans, dp[m][i][j]) ;
printf("%d\n", ans) ;
}
}
相似题:传纸条
link
选两条路径,走过两遍的格子只算一次
记录状态:走了\(i\)步,一个到\((x1,y1),\)一个到\((x2,y2)\)
发现因为起点相同,位置和\(i\)有关,关系式\(x1+y1=x2+y2=i+2\)一直满足
省去2个维度 变成\(dp[i][x1][x2]\)
枚举4个方向,判断是否会踩到一起即可
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std ;
typedef long long ll ;
const int N = 60 ;
int n, m ;
int a[N][N] ;
int dp[2 * N][N][N] ;
void ckmax(int &a, int b) {
a = max(a, b) ;
}
int main() {
scanf("%d%d", &n, &m) ;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]) ;
dp[0][1][1] = a[1][1] ;
for (int i = 0; i < n + m - 2; i++)
for (int x1 = 1; x1 <= min(n, i + 1); x1++)
for (int x2 = 1; x2 <= min(n, i + 1); x2++) {
int y1 = i + 2 - x1, y2 = i + 2 - x2 ; // (x1, y1) (x2, y2)
if (y1 < 1 || y1 > m || y2 < 1 || y2 > m) continue ;
// 下下
if (x1 == x2) ckmax(dp[i + 1][x1 + 1][x2 + 1], dp[i][x1][x2] + a[x1 + 1][y1]) ;
else ckmax(dp[i + 1][x1 + 1][x2 + 1], dp[i][x1][x2] + a[x1 + 1][y1] + a[x2 + 1][y2]) ;
// 下右
if (x1 + 1 == x2) ckmax(dp[i + 1][x1 + 1][x2], dp[i][x1][x2] + a[x1 + 1][y1]) ;
else ckmax(dp[i + 1][x1 + 1][x2], dp[i][x1][x2] + a[x1 + 1][y1] + a[x2][y2 + 1]) ;
// 右下
if (x1 == x2 + 1) ckmax(dp[i + 1][x1][x2 + 1], dp[i][x1][x2] + a[x1][y1 + 1]) ;
else ckmax(dp[i + 1][x1][x2 + 1], dp[i][x1][x2] + a[x1][y1 + 1] + a[x2 + 1][y2]) ;
// 右右
if (x1 == x2) ckmax(dp[i + 1][x1][x2], dp[i][x1][x2] + a[x1][y1 + 1]) ;
else ckmax(dp[i + 1][x1][x2], dp[i][x1][x2] + a[x1][y1 + 1] + a[x2][y2 + 1]) ;
}
printf("%d\n", dp[n + m - 2][n][n]) ;
}

浙公网安备 33010602011771号