Luogu 2258 [NOIP2014] 子矩阵
被普及组虐了,感觉💊啊。
因为这个子矩阵可以分开取,所以我们可以很方便地把两个子矩阵组合起来。因为$m$很小,所以一行要选哪$j$个可以直接状压起来,但是我们只要$m$个格子选$c$个的方案数,直接用$2^m$来记录会出现很多冗余状态,因为我们选取的所有状态一共只有$\binom{m}{c}$个,所以我们先预处理一下所需要的状态数,最多不超过$\binom{16}{8}\ = \ 12870$个。
我们只要先预处理一下每一行左右的格子产生的贡献,以及每两行上下的格子产生的贡献就可以转移了。
这样子就可以想到$dp$,我们设$f_{i, j, k}$表示当前已经选了$i$行,选的行的状态为$j$,以第$k$行为结尾的最小价值,设$h_{i, j}$表示当前选到了第$i$行,选的状态为$j$的左右的格子产生的贡献,$g_{i, j, k}$表示从第$i$行转移到第$j$行,选的格子的状态为$j$上下两行的格子产生的贡献(并保证$i > j$),有转移方程:
$f_{i, j, k} = min(f_{i - 1, j, t} + h_{k, j} + g_{k, t, j})$ $( 1\leq t < k)$。
$f$的第一维可以滚掉。
时间复杂度$O(rn^2\binom{m}{c})$。
结果一开始$n$和$m$写错了……
Code:
#include <cstdio> #include <cstring> using namespace std; const int N = 18; const int M = 13005; const int S = (1 << 16) + 5; const int inf = 0x3f3f3f3f; int n, m, r, c, a[N][N], tot = 0, q[S]; int f[2][M][N], h[N][M], g[N][N][M]; inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } inline int abs(int x) { return x > 0 ? x : -x; } inline void chkMin(int &x, int y) { if(y < x) x = y; } void dfs(int now, int s, int stp) { if(stp == c) { q[++tot] = s; return; } // if(now > m) return; for(int i = now; i <= m; i++) dfs(i + 1, s | (1 << (i - 1)), stp + 1); } inline int getR(int k, int s) { int pos[N], cnt = 0, res = 0; for(int i = 0; i < m; i++) if((s >> i) & 1) pos[++cnt] = i + 1; for(int i = 1; i <= cnt; i++) { if(i > 1) res += abs(a[k][pos[i]] - a[k][pos[i - 1]]); if(i < cnt) res += abs(a[k][pos[i]] - a[k][pos[i + 1]]); } return res; } inline int getC(int now, int pre, int s) { int pos[N], cnt = 0, res = 0; for(int i = 0; i < m; i++) if((s >> i) & 1) pos[++cnt] = i + 1; for(int i = 1; i <= cnt; i++) res += abs(a[now][pos[i]] - a[pre][pos[i]]); return res * 2; } inline void trans(int now) { int len = 0, bit[N]; memset(bit, 0, sizeof(bit)); for(int tmp = now; tmp > 0; tmp >>= 1) bit[++len] = (tmp & 1); for(int i = 1; i <= m; i++) printf("%d", bit[i]); putchar(' '); } int main() { // freopen("testdata.in", "r", stdin); read(n), read(m), read(r), read(c); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) read(a[i][j]); dfs(1, 0, 0); /* printf("\n"); for(int i = 1; i <= tot; i++) trans(q[i]); printf("\n"); */ for(int i = 1; i <= n; i++) for(int j = 1; j <= tot; j++) h[i][j] = getR(i, q[j]); /* for(int i = 1; i <= n; i++) { printf("%d: ", i); for(int j = 1; j <= tot; j++) printf("%d ", h[i][j]); printf("\n"); } */ for(int i = 1; i <= tot; i++) for(int j = 1; j <= n; j++) for(int k = 1; k < j; k++) g[j][k][i] = getC(j, k, q[i]); memset(f, 0x3f, sizeof(f)); for(int i = 1; i <= tot; i++) for(int j = 1; j <= n; j++) f[1][i][j] = h[j][i]; for(int i = 2; i <= r; i++) { int now = i & 1, pre = (i - 1) & 1; for(int j = 1; j <= tot; j++) for(int k = 1; k <= n; k++) for(int t = 1; t < k; t++) if(f[pre][j][t] != inf) chkMin(f[now][j][k], f[pre][j][t] + h[k][j] + g[k][t][j]); for(int j = 1; j <= tot; j++) for(int k = 1; k <= n; k++) if(f[pre][j][k] != inf) f[pre][j][k] = inf; } int ans = inf; for(int i = 1; i <= tot; i++) for(int j = 1; j <= n; j++) chkMin(ans, f[r & 1][i][j]); printf("%d\n", ans / 2); return 0; }