9.27 动态规划测试 题解
9.27 动态规划测试 题解
A. 矩阵(合并 DP)
题意
矩阵乘法是定义在矩阵上的二元运算,支持结合律但不支持交换律。一个 \(m\times n\) 的矩阵 \(A\) 和一个 \(n\times p\) 的矩阵 \(B\) 相乘,需要进行 \(m\times n\times p\) 次乘法运算,并得到一个 \(m\times p\) 的矩阵。
现将给出行数 \(m_i\) 和列数 \(n_i\) 的 \(N\) 个矩阵相乘,求进行乘法运算的最小次数。
\(2\le N\le500\)。
思路
合并 DP,设 \(dp[i,j]\) 表示将第 \(i\) 到第 \(j\) 个矩阵合并为一个矩阵,需要进行乘法运算的最小次数。
状态转移方程为 \(dp[i,j]=\min\limits_{i\le k<j}\{dp[i,k]+dp[k+1,j]+m_i\times n_k\times n_j\}\)。
时间复杂度 \(O(n^3)\)。
代码
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
int const N = 550;
int nn, dp[N][N], m[N], n[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> nn;
f(i, 1, nn) cin >> m[i] >> n[i];
f(p, 1, nn - 1) //枚举区间长度
for (int i = 1, j = p + 1; j <= nn; ++i, ++j) {
dp[i][j] = 0x3f3f3f3f; //初值
f(k, i, j - 1)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + m[i] * n[k] * n[j]);
}
cout << dp[1][nn] << '\n';
return 0;
}
B. 时态同步(树上 DFS)
- (题解是用 DP 做的,不过我考试时想的是 DFS)
三年 OI 一场空,不开long long见祖宗
题意
给出一棵 \(n\) 个节点的树及其根节点 \(S\),每条边有一个通过时间 \(t_i\)。你可以进行一次「操作」,使得某条边的通过时间增加一秒。
你需要让根节点 \(S\) 到每个叶节点的所需时间都相等,且「操作」次数最少。
\(n\le 5\times 10^5,t_i\le 10^6\)。
思路
容易证明:将所有叶子节点所需时间都变为最大的时间最优,并且贪心地操作深度较小的边比深度较大的边更优。
具体来说,先跑一遍 DFS,处理出边 \(i\) 下方的所有叶子节点中,所需最大的时间 \(maxx_i\),以及所有叶子节点中最大的时间 \(maxt\)。
对于边 \(i\),\(maxt-maxx_i\) 即为需要「操作」的次数。由于对上方的边进行「操作」时,下方的边的 \(maxx\) 值也会发生改变,即如果设上方已经「操作」了 \(x\) 次,那么这条边只需「操作」 \(maxt-x-maxx_i\) 次。
所以在跑第二遍 DFS 的过程中,我们传入一个参数 \(remain\) 表示还需要「操作」多少次(使下方所需时间最大的叶子节点的时间变为 \(maxt\)),每次调用时 \(remain\leftarrow maxx_i\),同时 \(ans\leftarrow ans+remain-maxx_i\),即对这条边「操作」了 \(remain-maxx_i\) 次。
代码
#include <cstdio>
#include <iostream>
#include <cctype>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
#define int ll
using namespace std;
typedef long long ll;
int const N = 5e5 + 10;
int n, S;
int head[N], cnt;
struct Edge{
int to, nxt, val, maxx;
} e[N << 1];
il void add(int from, int to, int val) {
e[++cnt].to = to, e[cnt].nxt = head[from], e[cnt].val = val, head[from] = cnt;
return;
}
int maxt;
void dfs(int x, int fa, int nw, int in_edge) {
int maxx = 0;
int child = 0;
for (int i = head[x]; i; i = e[i].nxt) {
int to = e[i].to, val = e[i].val;
if (to == fa) continue;
++child;
dfs(to, x, nw + val, i);
maxx = max(e[i].maxx, maxx);
}
if (!child) {
maxt = max(maxt, nw);
e[in_edge].maxx = nw;
return;
}
if (x == S) return;
e[in_edge].maxx = maxx;
return;
}
int ans;
void dfs2(int x, int fa, int remain) {
for (int i = head[x]; i; i = e[i].nxt) {
int to = e[i].to, maxx = e[i].maxx;
if (to == fa) continue;
dfs2(to, x, maxx);
ans += remain - maxx;
}
return;
}
signed main() {
scanf("%lld%lld", &n, &S);
int x, y, z;
f(i, 1, n - 1) {
scanf("%lld%lld%lld", &x, &y, &z);
add(x, y, z), add(y, x, z);
}
dfs(S, -1, 0, -1);
dfs2(S, -1, maxt);
printf("%lld\n", ans);
return 0;
}
C. 葡萄(状态压缩 DP)
题意
给定一列 \(n\) 个数 \(c_1,c_2,\dots,c_n\),现在从中取出一些数,规则是:每连续 \(k\) 个数中最少取 \(a\) 个,最多取 \(b\) 个。
现想要使取出的数的总和减掉剩余的数总和最大。求出这个最大值。
对于 \(30\%\) 的数据,\(a=0,b=k\);
对于 \(100\%\) 的数据,\(n\le 10^4,0\le a\le b\le k\le 10,|c_i|\le 10^4\)。
思路
由于要使取出的数的总和减掉剩余的数总和最大,设取出的数的总和为 \(x\),所有数的总和为 \(sum\),那么答案为 \(x-(sum-x)=2x-sum\)。所以只需要最大化 \(x\) 即可。
对于 \(30\%\) 的数据,可以任意取数,所以直接贪心地取所有正数即可。
对于 \(100\%\) 的数据,注意到 \(a,b,k\) 的范围很小,考虑状态压缩 DP。
设 \(dp[i,sta]\) 表示当 \(c[i-k+1]\) 到 \(c[i]\) 的状态为 \(sta\) 时,在前 \(i\) 个数中取若干数所得的最大值。
考虑如何转移状态:
显然取或者不取 \(c[i-k+1]\) 到 \(c[i]\) 由所枚举的 \(sta\) 决定,即如果 sta & 1 == 1 那么取 \(c[i]\),否则不取。
所以我们需要讨论的只有是否取 \(c[i-k]\)。
如果取,那么前一个的状态为 (sta >> 1) | (1 << (k - 1));否则为 sta >> 1。
所以状态转移方程为:
dp[i][sta] = max(dp[i - 1][sta >> 1], dp[i - 1][(sta >> 1) | (1 << (k - 1))]) + c[i] * (sta & 1)。
(注意 c[i] * sta & 1 一定要写在外面(这个小错误我 xx 调了一个小时)
由于有 \(a,b\) 的限制,所以我们先 DFS 预处理出所有合法的 \(sta\)。
然后枚举 \(sta\),暴力枚举每一位,预处理出所有的 \(dp[k,sta]\)。
然后枚举 \(i\),枚举所有合法的 \(sta\),进行状态转移。
答案为 \(\max\{dp[n][sta]\}\),其中 \(sta\) 为所有合法的状态。
别忘了答案不是单纯的 \(x\),而是 \(2x-sum\)。
可以顺便滚动一下数组。
时间复杂度约为 \(O(2^kn)\)。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
int const N = 1e4 + 10;
int n, k, a, b, c[N], sum, ans;
int dp[2][1200];
int status[N], tot;
void _init(int d, int pcnt, int sta) {
if (d >= k) {
if (pcnt >= a && pcnt <= b)
status[++tot] = sta;
return;
}
_init(d + 1, pcnt + 1, sta ^ (1 << d));
_init(d + 1, pcnt, sta);
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> k >> a >> b;
f(i, 1, n) cin >> c[i], sum += c[i];
if (a == 0 && b == k) { //30%
f(i, 1, n) if (c[i] > 0) ans += c[i];
cout << ans * 2 - sum << '\n';
return 0;
} else if (a == 0 && b == 0) { //特判 (竟然有20%
cout << -sum << '\n';
return 0;
}
_init(0, 0, 0);
// sort(status + 1, status + tot + 1);
memset(dp, 128, sizeof dp); //初值
int nw = 1, pr = 0;
f(i, 1, tot) {
dp[1][status[i]] = 0;
f(j, 0, k - 1)
if ((status[i] >> j) & 1)
dp[1][status[i]] += c[k - j];
}
f(i, k + 1, n) {
nw ^= 1, pr ^= 1;
memset(dp[nw], 128, sizeof dp[nw]); //初值
f(j, 1, tot) {
int sta = status[j];
dp[nw][sta] = max(dp[pr][sta >> 1], dp[pr][(sta >> 1) | (1 << (k - 1))]) + c[i] * (sta & 1);
}
}
ans = 0x80808080; //初值
f(i, 1, tot) ans = max(ans, dp[nw][status[i]]);
cout << ans * 2 - sum << '\n';
return 0;
}
/*
6 4 3 4
-5 2 -4 2 -1 3
9
*/
D. 矩形
TO DO...

浙公网安备 33010602011771号