发现 $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 ",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; }