题目
T(T<=40)组样例,每次给出一个n*m(n<=12,m<=2000)的矩阵a[],aij的权值为[1,1e5]的整数,
对每一列,你都可以选择对该列进行任意次(含0次)的循环移位,所有列都操作完后,
对于移位后的矩阵,取每一行的最大值ri,对其求和,
求和的最大值
思路来源
https://blog.csdn.net/weixin_44231553/article/details/100859873
题解
把列按列的最大值排降序,
设n<=m,显然最大值之和不会小于此时独立含最大值的n列,
因为如果选取了后面的列的某元素,不如换成前面的独立列更优
所以考虑只对前n列作状压dp,dp[i][j]表示前i列覆盖状态为j时的最大值,规模是dp[n][1<<n]的
加入第i行的贡献的时候,枚举j的子集S,把S分给第i行的mx[i][S],从dp[i-1][j^S]转移即可
枚举第i行,枚举子集状态,复杂度是
mx[i][S]表示第i行状态为S时的最大值,
可以通过枚举一维行i,枚举循环移位量k,通过k重新求一下数组,
S从小到大通过lowbit O(1)求和转移,复杂度是的,
考虑下排序的时间,再乘个T,足矣
为了统一n>m的情形,
只需把最后需要状压的列的上界设成min(n,m)即可
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=12,M=1<<12,Q=2e3+5;
//dp[i][j]:前i列state为j时的最大sum 枚举子集
//mx[i][j]:第i列state为j时的和
//p:下标用于排序 big:列的最大值
//b tmp:用于确定偏移量固定时 当前列子集的最大值
int t,n,m,v,dp[N+1][M],mx[N][M],p[Q],big[Q],b[M],tmp[M];
vector<int>a[Q];
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int j=0;j<m;++j){
a[j].clear();
p[j]=j;
big[j]=-1;
}
for(int i=0;i<n;++i){
for(int j=0;j<m;++j){
scanf("%d",&v);
a[j].push_back(v);
big[j]=max(big[j],v);
}
}
sort(p,p+m,[](int i,int j){return big[i]>big[j];});
memset(mx,0,sizeof mx);
memset(dp,0,sizeof dp);
int up=min(n,m);
for(int i=0;i<up;++i){//rank
int col=p[i];//实际列
for(int k=0;k<n;++k){//偏移量
for(int l=0;l<n;++l){
b[1<<l]=a[col][(l+k)%n];
}
tmp[0]=0;
for(int j=1;j<(1<<n);++j){
int lb=j&(-j);
tmp[j]=tmp[j^lb]+b[lb];
mx[i][j]=max(mx[i][j],tmp[j]);
}
}
}
for(int i=0;i<up;++i){
for(int j=1;j<(1<<n);++j){
for(int S=j;;S=(S-1)&j){//考虑S=j或S=0时 只由一个集合拼成 故均合法
dp[i+1][j]=max(dp[i+1][j],dp[i][S]+mx[i][j^S]);
if(S==0)break;
}
}
}
printf("%d\n",dp[up][(1<<n)-1]);
}
return 0;
}