题意也能够理解为这样一个过程:c++
对于每一列,将其旋转后选出若干行上的数,要求与以前的行都不一样ide
用$g_{i,S}$表示第$i$列选出的行数集合为$S$的最大和,$f_{i,S}$表示前$i$列$S$中的行已经选择的最大和,转移经过枚举子集,复杂度为$o(Qm3^{n})$spa
关于$g_{i,S}$的计算能够先预处理$sum_{i,S}$表示第$i$列$S$这些行的和(不旋转),接下来枚举旋转,用二进制简单维护,复杂度为$o(Qnm2^{n})$it
(代码中利用的是找到其最小表示法,并直接从最小表示法处转移,若是定义轮换相同,则本质不一样的串根据polya定理大约为$o(\frac{2^{n}}{n})$,暴力$o(n^{2})$统计复杂度相同)class
进一步的,只须要选择最大值最大的$n$列(相同任取)便可,若是在另一列选择,那么这$n$列中必定有一个列被选择,同时那一列中能够任意旋转,用该列最大值来替换这“另一列”必定不劣二进制
最终时间复杂度为$o(Qn3^{n}+Qn^{2}2^{n})$,能够经过im
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 12 4 #define M 2005 5 int t,n,m,a[M][N],mx[1<<N],f[N+5][1<<N]; 6 pair<int,int>b[M]; 7 int main(){ 8 scanf("%d",&t); 9 while (t--){ 10 scanf("%d%d",&n,&m); 11 for(int i=0;i<n;i++) 12 for(int j=1;j<=m;j++)scanf("%d",&a[j][i]); 13 for(int i=1;i<=m;i++){ 14 b[i].first=0; 15 for(int j=0;j<n;j++)b[i].first=max(b[i].first,a[i][j]); 16 b[i].first*=-1; 17 b[i].second=i; 18 } 19 sort(b+1,b+m+1); 20 m=min(n,m); 21 for(int ii=1;ii<=m;ii++){ 22 int i=b[ii].second; 23 for(int j=0;j<(1<<n);j++){ 24 mx[j]=0; 25 int s=j; 26 for(int k=1;k<n;k++)s=min(s,(j>>k)+((j&((1<<k)-1))<<(n-k))); 27 if (s==j){ 28 for(int k=0;k<n;k++){ 29 int s=0; 30 for(int l=0;l<n;l++) 31 if (j&(1<<l))s+=a[i][(k+l)%n]; 32 mx[j]=max(mx[j],s); 33 } 34 } 35 else mx[j]=mx[s]; 36 } 37 for(int j=0;j<(1<<n);j++){ 38 f[ii][j]=0; 39 for(int k=j;;k=((k-1)&j)){ 40 f[ii][j]=max(f[ii][j],f[ii-1][k]+mx[j^k]); 41 if (!k)break; 42 } 43 } 44 } 45 printf("%d\n",f[m][(1<<n)-1]); 46 } 47 return 0; 48 }