codeforces 1209E2 Rotate Columns (hard version)

题目传送门:codeforccesluogu

思路

状压 dp ,贪心。

贪心

对于所有列,只有列中最大值在所有列的最大值中前 \(n\) 大才可能对答案有贡献。
证明:若有非前 \(n\) 大的列对某行最大值产生了贡献,则用没有被取的前 \(n\) 大的列代替该行一定更优。所以只有列中最大值在所有列的最大值中前 \(n\) 大才可能对答案有贡献。

状压 dp

定义状态 \(S\) ,表示所有行中某些行已经确认最大值的状态,用一个二进制数 \(k\) 表示 \(S\)\(k\) 的第 \(i\) 位表示第 \(i\) 行是否已经确定最大值。定义 \(w_i\)\(i\) 为一状态,表示该状态的最大全取当前列的值。

可以假设目前那些行已经确认最大值,它们形成了状态 \(j\)
定义数组 \(f_{i, j}\) 表示前 \(i\) 列,状态为 \(j\) 时的最大值。
容易推出状态转移方程式:\(f_{i, j} = \max_k^{k \in j}(f_{i - 1, k} + w_{j \operatorname{xor} k})\)
意思为:状态 \(j\) 由状态 \(j\) 的一个子状态 \(k\) 转移而来,而 \(k\) 中没有确定的行,即为 \(k \operatorname{xor} j\) 的状态需要确认,所以加上 \(w_{k \operatorname{xor} j}\) 来补足该状态的值(因为前面是 \(k\) 状态已经固定了,所以要用当前列来补),得到 \(f_{i, j}\)

细节

转移时可以用到枚举子集的方法。
因为枚举子集时枚举不到空集,所以要在转移前先赋值。

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
typedef long long LL;

using namespace std;

const int M = 2005, N = 15;

#define LF(i, __l, __r) for (int i = __l; i <= __r; i++)
#define RF(i, __r, __l) for (int i = __r; i >= __l; i--)

struct node{ int a[N], mx; } a[M];
int t, n, m, w[1 << N];
LL f[N][1 << N];

bool cmp(node a, node b) { return a.mx > b.mx; }

int main() {
    scanf("%d", &t);

    while (t--) {
        memset(f, 0, sizeof(f));
        memset(a, 0, sizeof(a));
        scanf("%d%d", &n, &m);
        LF(i, 0, n - 1) LF(j, 0, m - 1) {
            scanf("%d", &a[j].a[i]);
            a[j].mx = max(a[j].mx, a[j].a[i]);
        }

        sort(a, a + m, cmp);

        LF(i, 0, min(n - 1, m - 1)) {
            LF(j, 0, (1 << n) - 1) { // 枚举状态 j ,预处理 w 数组方便后面处理
                w[j] = 0;

                LF(k, 0, n - 1) {
                    int res = 0;
                    LF(l, 0, n - 1) if ((1 << l) & j) res += a[i].a[(l + k) % n];
                    w[j] = max(w[j], res);
                }
            }

            LF(j, 0, (1 << n) - 1)
                if (i == 0) f[0][j] = w[j];
                else {
                    f[i][j] = f[i - 1][j];
                    for (int k = j; k; k = (k - 1) & j) f[i][j] = max(f[i][j], f[i - 1][k] + w[j ^ k]); // 枚举子集
                }
        }

        printf("%lld\n", f[min(n - 1, m - 1)][(1 << n) - 1]);
    }
    return 0;
}

“人生大病只是一傲字。” —— 《传习录》

posted @ 2024-07-28 20:47  FRZ_29  阅读(6)  评论(0编辑  收藏  举报