https://codeforces.com/contest/1209/problem/E2

题意:

给定一个N×M 的矩阵,你可以对每一列的数字进行任意次的旋转操作(即整体向上或者整体向下)。输出在做出任意次旋转操作后每一行的最大值之和。


解题思路:看到n的范围很小,可以联想到状压DP来解,设dp【i】为状态i的最大值,所谓状态i,就是在i的二进制下,某位置为1则表示该行已找到最大值,为0则没有找到最大值,那么可以进行子集DP了。还有值得注意的是,我们最少可以在min(n,m)列中找打答案,所以先按每列的最大值排序,取前min(n,m)列就好了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+5;
int dp[1<<12];
int f[1<<12];
int f1[1<<12];
struct st{
	int id,w;
	bool operator < (const st & tem)const{
		return w>tem.w;
	}
}stm[maxn];
int a[15][maxn];
int use[maxn];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;i++){
			for(int j=0;j<m;j++){
				scanf("%d",&a[i][j]);
			}
		}
		for(int i=0;i<m;i++){
			int tem=0;
			for(int j=0;j<n;j++){
				tem=max(tem,a[j][i]);
			}
			stm[i].id=i;
			stm[i].w=tem;
		}
		sort(stm,stm+m);
		memset(use,0,sizeof(use));
		memset(dp,0,sizeof(dp));
		for(int i=0;i<n&&i<m;i++){
			use[stm[i].id]=1;
		}
		for(int i=0;i<m;i++){
			if(use[i]==0){
				continue;
			}
			for(int k=0;k<n;k++){
				for(int j=0;j<(1<<n);j++){
					f1[j]=max(f1[j],f[j]);
					f[j]=dp[j];
					for(int kk=0;kk<n;kk++){
						int tem=(k+kk)%n;
						if((j&(1<<tem))){
							f[j]=max(f[j],f[j^(1<<tem)]+a[kk][i]);
						}
					}
				}
			}
			for(int j=0;j<(1<<n);j++){
				dp[j]=max(f1[j],f[j]);
				f1[j]=0;
				f[j]=0;
			}
		}
		printf("%d\n",dp[(1<<n)-1]);
	}
	return 0;
}

  

01-15 01:38