DP做题记录(四)(2025.5.1 - 2025)

洛谷 P2517 [HAOI2010] 订货

一道比较简单的 DP 题目,听说有费用流做法,但蒟蒻不会。

我们设 \(dp_{i, j}\) 表示考虑前 \(i\) 个月,其中第 \(i\) 个月卖完后还剩 \(j\) 个货物的最小花费。那么我们枚举上个月还剩 \(k\) 个,那么这个月就买入了 \(U_i + j - k\) 个货物。而上个月剩下的 \(k\) 个货物还需要花费 \(k \times m\) 的存储费,因此 \(dp_{i, j} = \displaystyle\min_{k = 0}^{j + U_i} (dp_{i - 1, k} + d_i \times (u_i + j - k) + k \times m)\)

现在来化简式子,我们把常量提到 \(\min\) 的外面,可以得到 \(dp_{i, j} = \displaystyle\min(dp_{i - 1, k} - k \times d_i + k \times m) + (j + U_i) \times d_i\),此时 \(\min\) 中是一段前缀,可以前缀和优化,然后这道题就做完了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 59, M = 1e4 + 9;
int u[N], d[N], minn[N][M], dp[N][M], n, m, s;
int main(){
	scanf("%d%d%d", &n, &m, &s);
	for(int i = 1; i <= n; i++)
		scanf("%d", &u[i]);
	for(int i = 1; i <= n; i++)
		scanf("%d", &d[i]);
	for(int i = 1; i <= n; i++)
		for(int j = 0; j <= s; j++){
			dp[i][j] = minn[i - 1][min(s, j + u[i])] + d[i] * j + d[i] * u[i];
			minn[i][j] = dp[i][j] + m * j - j * d[i + 1];
			if(j > 0)
				minn[i][j] = min(minn[i][j], minn[i][j - 1]);
		}
	printf("%d", dp[n][0]);
	return 0;
}

洛谷 P3158 [CQOI2011] 放棋子

好玩的计数 DP 题目。

首先,每行每列只能有一种颜色的棋子,因此不会出现一个位置既可以放这种颜色的棋子,也可以放另一种颜色的棋子的情况,因此每种颜色的摆放相互独立。

我们设 \(f_{i, j, k}\) 表示前 \(k\) 种颜色的棋子占据了 \(i\)\(j\) 列的方案数。我们考虑之前 \(k - 1\) 种颜色的棋子占据了 \(l\)\(r\) 列(必须保证 \((i - l) \times (j - r) \geq c_k\),不然就会出现某个棋子必须摆在这 \(l\)\(r\) 列已经摆上了其他棋子的行列,不符合题意),那么 \(f_{i, j, k} = \displaystyle\sum_{l = 0}^{i - 1} \displaystyle\sum_{r = 0}^{j - 1} f_{l, r, k - 1} \times \binom{n - l}{i - l} \times \binom{m - r}{j - r}\) 乘上将这 \(c_k\) 个棋子摆入这 \(i - l\)\(j - r\) 列的方案数。

这似乎不好用组合数学直接得出,我们继续考虑 \(DP\)。设 \(g_{i, j, k}\) 表示用 \(k\) 枚同色棋子占据 \(i\)\(j\) 列的方案数。我们首先考虑在这 \(i \times j\) 个位置中放入这 \(k\) 个棋子,随便放的方案数是 \(\displaystyle\binom{i \times j}{k}\),但是有可能没有放满,因此我们钦定只放满了 \(l\)\(r\) 列,那么就要将答案减去 \(g_{l, r, k} \times \displaystyle\binom il \times \binom jr\),因此 \(g_{i, j, k} = \displaystyle\binom{i \times j}{k} - \sum_{l = 1}^{i - 1} \sum_{r = 1}^{j - 1} g_{l, r, k} \times \binom il \times \binom jr\)(其实 \(l\)\(r\) 中有且仅有一个顶到上界也可以),此时将 \(g_{i, j, c_k}\) 带入原先 \(f_{i, j, k}\) 的转移方程,然后这道题就做完了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 30 + 9, M = 9e2 + 9, MOD = 1e9 + 9;
int binom[M][M], c[N], g[N][N], f[N][N][M], n, m, t;
signed main(){
	scanf("%lld%lld%lld", &n, &m, &t);
	for(int i = 1; i <= t; i++)
		scanf("%lld", &c[i]);
	for(int i = 0; i <= n * m; i++)
		binom[i][0] = 1;
	for(int i = 1; i <= n * m; i++)
		for(int j = 1; j <= n * m; j++)
			binom[i][j] = (binom[i - 1][j] + binom[i - 1][j - 1]) % MOD;
	f[0][0][0] = 1;
	for(int k = 1; k <= t; k++){
		memset(g, 0, sizeof(g));
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= m; j++){
				if(i * j >= c[k]){
					g[i][j] = binom[i * j][c[k]];
					for(int l = 1; l <= i; l++){
						for(int r = 1; r <= j; r++){
							if(l < i || r < j)
								g[i][j] = (g[i][j] - g[l][r] * binom[i][l] % MOD * binom[j][r] % MOD + MOD) % MOD;
						}	
					}
				}
			}	
		}
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= m; j++){
				for(int l = 0; l <= i - 1; l++){
					for(int r = 0; r <= j - 1; r++){
						if((i - l) * (j - r) >= c[k])
							f[i][j][k] = (f[i][j][k] + f[l][r][k - 1] * g[i - l][j - r] % MOD * binom[n - l][i - l] % MOD * binom[m - r][j - r] % MOD) % MOD;
					}
				}
			}
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			ans = (ans + f[i][j][t]) % MOD;
	printf("%lld", ans);
	return 0;
}

洛谷 P9129 [USACO23FEB] Piling Papers G

posted @ 2025-05-01 16:21  Orange_new  阅读(20)  评论(0)    收藏  举报