线性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. \(1\)行随便加
  2. 对于第\(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]) ;
}

posted @ 2023-09-10 09:58  哈奇莱特  阅读(30)  评论(0)    收藏  举报