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;
}
View Code

 

posted @ 2018-09-19 19:20  CzxingcHen  阅读(181)  评论(0编辑  收藏  举报