传送门

发现 $n$ 很小,考虑状压 $dp$,但是如果强行枚举列并枚举置换再转移复杂度太高了

考虑推推结论,发现我们只要保留列最大值最大的 $n$ 列即可,证明好像挺显然:

假设我们让列最大值比较小的列贡献给某一行,那么由抽屉原理发现这意味着列最大值排名前 $n$ 的某一列一定没对答案贡献,

此时我们完全可以用那一列的最大值替换原本这一列对答案的贡献,这种情况同样可以推广到列最大值比较小的列贡献给多行的情况

所以证明就完成了

保留完最大的 $n$ 列,然后直接暴力 $dp$,设 $f[i][S]$ 表示考虑完前 $i$ 列,确定了的行的集合为 $S$ 时的最大值

那么转移就枚举子集,比子集多出来确定的行即为第 $i$ 列对答案贡献的行

增加的贡献设为 $mx[i][S]$ 表示第 $i$ 列,贡献的集合为 $S$ 时的最大值,这个可以枚举置换 $2^n \cdot n^2$ 预处理

然后枚举子集进行 $dp$ 的复杂度为 $3^n \cdot n$ ,总复杂度算一下达到了 $1e8$ 的级别

但是 $CF$ 评测机比较快并且这题时间限制比较充裕,稳得要死.jpg

多组数据注意要清空

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=13,M=2007;
int T,n,m,mx[N][1<<N],f[N][1<<N];
struct dat {
    int a[N],b[N];
    inline bool operator < (const dat &tmp) const {
        for(int i=n-1;i>=0;i--)
            if(b[i]!=tmp.b[i]) return b[i]<tmp.b[i];
        return 0;
    }
}d[M];
int main()
{
    T=read();
    while(T--)
    {
        n=read(),m=read();
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++) d[j].a[i]=d[j].b[i]=read();
        for(int i=0;i<m;i++) sort(d[i].b,d[i].b+n);
        sort(d,d+m); reverse(d,d+m); int Mx=(1<<n)-1;
        for(int i=0;i<n;i++)
            for(int j=1;j<=Mx;j++) mx[i][j]=0;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                for(int k=1;k<=Mx;k++)
                {
                    int t=0;
                    for(int l=0;l<n;l++)
                        if((k>>l)&1) t+=d[i].a[(j+l)%n];
                    mx[i][k]=max(mx[i][k],t);
                }
        for(int i=0;i<n;i++)
            for(int j=0;j<=Mx;j++) f[i][j]=(i==0 ? mx[i][j] : 0);
        for(int i=1;i<n;i++)
            for(int o=0;o<=Mx;o++)
            {
                f[i][o]=mx[i][o];
                for(int p=o;p;p=(p-1)&o)
                    f[i][o]=max(f[i][o],f[i-1][p]+mx[i][o^p]);
            }
        printf("%d\n",f[n-1][Mx]);
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++) d[i].a[j]=d[i].b[j]=0;//这里要记得清空啊
    }
    return 0;
}