行很少,列很多。
最终的答案只跟每一行有关,所以有些列是没用的。
或者换个角度理解,我们按一列上的最大数将这 (m) 列排序,把第一列上的最大数放在第 1 行,第二列上的最大数房子第 2 行,……,把第 (n) 列上的最大数放在第 (n) 行。于是我们发现第 (n+1sim m) 列甚至连最大数都是没用的,况且前 (n) 列不仅会用到最大数,其它数也都有用到的可能,所以可能有用的列数只有 (min(n,m)) 条。
接下来就考虑状压 DP,考虑状态 (f_{i,j}) 表示处理到排序后的第 (i) 列,行上的状态为 (j)(1 表示已经确定了最大数,0 表示没有确定最大数)中已经确定最大数的最大和。
考虑转移,枚举子集,并计算子集的最大贡献,复杂度为 (O(3^nn^3)) 但这是无法接受的。
我们发现瓶颈在于计算子集的最大贡献需要 (O(n^2)) 的复杂度,而一个子集可能会被计算多次,于是考虑提前预处理,得到复杂度为 (O(3^n n+2^nn^3))。
代码:
#include<bits/stdc++.h>
#define log(a) cerr<<" 33[32m[DEBUG] "<<#a<<'='<<(a)<<" @ line "<<__LINE__<<" 33[0m"<<endl
#define LL long long
#define SZ(x) ((int)x.size()-1)
#define ms(a,b) memset(a,b,sizeof a)
#define F(i,a,b) for(int i=(a);i<=(b);++i)
#define DF(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
inline int read(){char ch=getchar(); int w=1,c=0;
for(;!isdigit(ch);ch=getchar()) if (ch=='-') w=-1;
for(;isdigit(ch);ch=getchar()) c=(c<<1)+(c<<3)+(ch^48);
return w*c;
}
template<typename T>inline void write(T x){
unsigned long long y=0;T l=0;
if(x<0)x=-x,putchar(45);
if(!x){putchar(48);return;}
while(x){y=y*10+x%10;x/=10;l++;}
while(l){putchar(y%10+48);y/=10;l--;}
}
template<typename T>inline void writeln(T x){write(x);puts("");}
template<typename T>inline void writes(T x){write(x);putchar(32);}
const int N=13,M=2010,K=(1<<N)+10;
int f[N][K],a[N][M],t[K];
struct Column{
int maxn,id;
inline bool operator<(const Column&t)const{
return maxn>t.maxn;
}
}c[M];
signed main(){
int _=read();while(_--){
int n=read(),m=read();
F(i,1,m)c[i].id=i,c[i].maxn=0;
F(i,1,n)
F(j,1,m){
a[i][j]=read();
c[j].maxn=max(c[j].maxn,a[i][j]);
}
sort(c+1,c+m+1);
F(i,1,min(n,m)){
F(j,0,(1<<n)-1){
t[j]=0;
F(k,0,n-1){
int sum=0;
F(l,0,n-1)
if((j>>l)&1)sum+=a[(l+k)%n+1][c[i].id];
t[j]=max(t[j],sum);
}
}
F(j,0,(1<<n)-1){
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][j^k]+t[k]);
}
}writeln(f[min(n,m)][(1<<n)-1]);
}
return 0;
}