这道题是一道DP题。
状态定义如下:d[i][j]表示从开始到第i-1列为止,全部已经变为dot,并且第i~i+3列状态为j时候的最小花费(由于第i~i+3一共16个位置,所以可以状态压缩,用一个int类型数j存储)。
想象我们对第i~i+3列进行操作的过程。假设这4列之前的状态为j,那么显然操作之后的新状态S<=j。所以每次操作都具有单调性。状态转移就比较明确了:
①当第i列全部是dot时,d[i][j]可以转变d[i+1][k],其中k是由j经过变化得到的i+1列~i+4列的状态。显然如果d[i][j]不能转变为d[i+1][j],那么我们需要对第i列添加子矩阵使得它将来能够转化为d[i+1][k],所以只要考虑对第i列操作的情况就可以了。
②为了降低每次由于子矩阵覆盖带来的额外复杂度,我们用一个mark[4]的vector来分别记录当子矩阵大小为p=i+1时候,将子矩阵放置在一个全为’*’的4x4矩阵的第一列后得到的状态。那么对于第二部分的状态转移,我们有:d[i][j&S]=min(d[i][j&S],d[i][j]+a[p])。
到这里,两种状态转移就完成了。下面贴上代码,124ms
#include<bits/stdc++.h>
using namespace std;
const int mx = 1e3 + 6;
const int mm = (1 << 16) - 1;
const int INF = 0x3f3f3f3f;
int n, a[4], st[mx];//位压缩
char s[4][mx];//读入字符串
int d[mx][mm + 6];//d[i][j]表示0~i-1行都已经变成dot 并且i~i+3行的状态是j
vector<int>mark[4];
int main()
{
scanf("%d", &n);
for (int i = 0; i < 4; i++)scanf("%d", &a[i]);
for (int i = 0; i < 4; i++)scanf("%s", s[i]);
for (int j = 0; j < n; j++) {
for (int i = 0; i < 4; i++) {
st[j] |= (s[i][j] == '*') << i;
}
}
memset(d, 0x3f, sizeof(d));
int S = 0;
for (int i = 0; i < 4; i++)S |= st[i] << (i * 4);
d[0][S] = 0;
for (int i = 1; i <= 4; i++) { //枚举方框的大小
for (int j = 0; j <= 4 - i; j++) {
mark[i - 1].push_back(mm);
int & SS = mark[i - 1].back();
for (int k = j; k < j + i; k++) {
for (int g = 0; g < i; g++) {
SS ^= 1 << (g * 4 + k); //预处理,使得每次覆盖操作可以在O(1)内完成
}
}
}
}
for (int i = 0; i < n; i++) {
for (int j = mm; j >= 0; j--) {
if (d[i][j] == INF)continue;//d[i][j]==INF表示该状态不可达
if (!(j & 15)) { //如果该状态可达,并且第i列全部都是dot
int SS = j >> 4 | (st[i + 4] << 12);//那么该状态可以转化成d[i+1][j&SS]
d[i + 1][SS] = min(d[i][j], d[i + 1][SS]);
}
for (int k = 1; k <= 4 && k + i <= n; k++) {
for (auto p : mark[k - 1]) {
d[i][j&p] = min(d[i][j&p], d[i][j] + a[k - 1]);
}
}
}
}
printf("%d\n", d[n][0]);
}
本文介绍了一种使用动态规划解决子矩阵覆盖问题的方法。通过状态压缩技术,定义了状态d[i][j],表示从开始到第i-1列已变为dot,且第i~i+3列为状态j时的最小花费。文章详细阐述了两种状态转移过程,并提供了实现代码。
322

被折叠的 条评论
为什么被折叠?



