@codeforces - [email protected] Rotate Columns (hard version)

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

给定一个 n*m 的矩阵 A。
定义一次操作为将矩阵的某一列竖着循环移位,你可以对任意列做任意次操作。
定义 ri 为第 i 行的最大值,最大化 r1 + r2 + ... + rn。

Input
第一行一个整数 t (1≤t≤40),表示数据组数。
每组数据第一行包含两个整数 n m (1≤n≤12,1≤m≤2000) 表示 A 矩阵的行数与列数。
接下来 n 行每行 m 个整数,表示 A 中的元素。(1≤ai,j≤10^5)。

Output
输出 t 个整数:每组数据的答案。

Example
Input
3
2 3
2 5 7
4 2 4
3 6
4 1 5 2 10 4
8 6 6 4 9 10
5 4 9 5 8 7
3 3
9 9 9
1 1 1
1 1 1
Output
12
29
27

Note
第一组数据,只需要将第三行进行操作,得到 r1 = 5, r2 = 7。
第二组数据,不需要进行任何操作,得到 r1 = r2 = 10, r3 = 9。

@[email protected]

n <= 12 暗示你状压。

每一行的最大值之和最大,其实可以理解为每一行任意选择一个数使它们的和最大。
于是我们可以不考虑元素是不是该行最大值。

定义 dp[i][s] 表示前 i 列已经被选择的行构成集合 s,显然可以滚动数组。
通过枚举第 i + 1 列循环移位的次数,以及第 i + 1 列选择哪些元素计入答案(枚举子集)就可以转移。
但是时间复杂度 O(t*3^n*n*m) 过不了。

一个没什么用的优化:在确定循环移位次数后,我们不枚举子集,而是逐个加入元素。这样就可以将 O(3^n) 降为 O(2^n*n)。
但是时间复杂度 O(t*2^n*n^2*m) 还是过不了,不过因为代码是这么写的所以提一下。

问题最关键的点在于,n 和 m 不是等阶的。
注意到当 m >= n 时,我们总是存在一个方案:选 n 列,使得这 n 列的最大值之和最大。
其实就是给 m 列按照每一列的最大值从大到小排序,取前 n 列。

可以证明只需要这 n 列就可以组合出最终答案。
假如你选择了这 n 列以外的列,共选出 k 行。那么这 n 列中就有 k 列没选到,可以将这 k 列的最大值去替换 n 列以外的 k 行。

于是时间复杂度降为 O(t*2^n*n^3 + T(sort))。

@accepted [email protected]

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 12;
const int MAXM = 2000;
int a[MAXN + 5][MAXM + 5], n, m;
int f[1<<MAXN], g[1<<MAXN], h[1<<MAXN], lg[1<<MAXN];
int lowbit(int x) {return x & -x;}
struct node{
    int key, id;
    friend bool operator < (node a, node b) {
        return a.key > b.key;
    }
}arr[MAXM + 5];
bool tag[MAXM + 5];
void solve() {
    scanf("%d%d", &n, &m);
    for(int j=1;j<=m;j++)
        tag[j] = false, arr[j].key = 0, arr[j].id = j;
    for(int i=0;i<n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d", &a[i][j]), arr[j].key = max(arr[j].key, a[i][j]);
    sort(arr + 1, arr + m + 1);
    for(int i=1;i<=m&&i<=n;i++)
        tag[arr[i].id] = true;
    int tot = (1<<n);
    for(int j=0;j<tot;j++) f[j] = 0;
    for(int j=0;j<n;j++) lg[1<<j] = j;
    for(int i=1;i<=m;i++) {
        if( !tag[i] ) continue;
        for(int j=0;j<tot;j++) h[j] = 0;
        for(int j=1;j<=n;j++) {
            for(int k=0;k<tot;k++)
                g[k] = f[k];
            for(int k=0;k<tot;k++) {
                int p = (tot - 1)^k;
                while( p ) {
                    int x = lowbit(p), y = lg[x];
                    g[k|x] = max(g[k|x], g[k] + a[y][i]);
                    p -= x;
                }
            }
            int tmp = a[0][i];
            for(int k=0;k<n-1;k++)
                a[k][i] = a[k + 1][i];
            a[n - 1][i] = tmp;
            for(int j=0;j<tot;j++)
                h[j] = max(h[j], g[j]), g[j] = 0;
        }
        for(int j=0;j<tot;j++) f[j] = h[j];
    }
    printf("%d\n", f[tot - 1]);
}
int main() {
    int t; scanf("%d", &t);
    while( t-- ) solve();
}

@[email protected]

感觉看完题解都不能理解昨天晚上在干什么。。。

这种通过分析最终答案的下界,以此压缩状态的题虽然比较少见,但是见过的。。。
果然还是我太菜。。。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11521194.html

时间: 2024-08-30 01:48:51

@codeforces - [email protected] Rotate Columns (hard version)的相关文章

@codeforces - [email&#160;protected] Mashmokh&#39;s Designed Problem

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵 n 个点的树,每个点的儿子是有序的. 现给定 m 次操作,每次操作是下列三种中的一种: (1)给定 u, v,询问 u, v 之间的距离. (2)给定 v, h,断开 v 到父亲的边,将 v 这棵子树加入到它的第 h 个祖先的最后一个儿子. (3)给定 k,询问在当前这棵树上

@codeforces - [email&#160;protected] T-Shirts

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 件 T-shirt,第 i 件 T-shirt 有一个 ci 和 qi,分别表示费用与质量. 同时有 k 个顾客,第 j 个顾客准备了 bj 的金钱去购买 T-shirt. 每个顾客的购买策略是相同的: 他会买他的资金范围内 q 值最大的一件,如果有多个选 c 最小的一件,每种

@codeforces - [email&#160;protected] Oleg and chess

目录 @description - [email protected] @[email protected] @part - [email protected] @part - [email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @description - [email protected] 给定一个 n*n 的棋

@codeforces - [email&#160;protected] Lucky Tickets

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 已知一个数(允许前导零)有 n 位(n 为偶数),并知道组成这个数的数字集合(并不一定要把集合内的数用完).求有多少种可能,使得这个数前半部分的数位和等于后半部分的数位和. 模 998244353. input 第一行两个整数:n k.表示这个数的位数以及组成这个数的数字集合大小.2

@codeforces - [email&#160;protected] Bandit Blues

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @[email protected] 求有多少个长度为 n 的排列,从左往右遍历有 a 个数比之前遍历的所有数都大,从右往左遍历有 b 个数比之前遍历的所有数都大. 模 998244323. input 一行三个整数 n

@codeforces - [email&#160;protected] Vus the Cossack and a Field

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n*m 的 01 矩阵,通过这个矩阵生成一个无穷矩阵,具体操作如下: (1)将这个矩阵写在左上角. (2)将这个矩阵每位取反写在右上角. (3)将这个矩阵每位取反写在左下角. (4)将这个矩阵写在右下角. (5)将得到的矩阵再作为初始矩阵,重复这些操作. 比如对于初始矩阵:

@codeforces - [email&#160;protected] Big Problems for Organizers

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个点连成一棵树,经过每条边需要花费 1 个单位时间. 现给出 m 次询问,每次询问给出两个点,需要求所有点同时出发,最终所有点到达这两个点之一的最小花费时间. input 第一行包含一个整数 n (2?≤?n?≤?100000) ,表示点数. 接下来 n-1 行每行两个 1~n 的

@codeforces - [email&#160;protected] Strongly Connected Tournament

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个选手参加了一场竞赛,这场竞赛的规则如下: 1.一开始,所有选手两两之间独立进行比赛(没有平局). 2.主办方将胜者向败者连边形成 n 个点的竞赛图. 3.主办方对这个竞赛图进行强连通分量缩点. 4.每一个强连通分量内部的选手重复步骤 1~3,直到每一个强连通分量内只剩一个选手.

@codeforces - [email&#160;protected] Koala and Notebook

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n 点 m 边的无向连通图,每条边的编号按照输入顺序依次为 1, 2, ..., m. 现从 1 号点出发,当经过编号为 i 的边时,将 i 写下来.因为写的数之间没有空隙,所以写下来的所有数最终会连成一个数. 对于每一个除 1 以外的点,当它作为终点时,最终连成的数最小是多