版权声明:若转载请附上原博客链接,谢谢! https://blog.csdn.net/Link_Ray/article/details/89102023
https://codeforces.com/gym/101853/problem/E
题意
给一个n×m
的矩阵,要求选出一些点,使得这些点不相邻,同时这些点的和要最大。相邻的意义是,行或列或对角的距离等于1。
1 <= n <= 16
题解
一看到n的范围就想到状压dp,这和铺瓷砖很类似,就是一层一层的转移状态。如果直接暴力枚举状态的话复杂度是
,肯定要T的,遇到这种问题,就要想想如果只考虑单行,那么合法状态有多少种,直接提取出单行合法的进行转移,可以预处理出发现只有2000种,那么复杂度就降为
。同时还可以预处理出每行每种状态的权值和这样转移就只需要O(1)
的时间。总时间复杂度是
代码
#include <bits/stdc++.h>
using namespace std;
int dp[18][1<<17];
int a[20][20];
int c[18][1<<17];
bool okrow(int x) {
if(x&(x>>1))
return false;
return true;
}
bool ok(int x,int y) {
if(x&y) return false;
if(x&(y>>1) || (x>>1)&y) return false;
return true;
}
int main() {
// cout << 2500*2500 << endl;
int t;
scanf("%d", &t);
vector<int> v;
while(t--) {
memset(dp, -0x3f3f3f, sizeof dp);
memset(c,0,sizeof c);
v.clear();
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
for(int j = 0; j < n; ++j)
scanf("%d", &a[i][j]);
int tot = (1<<n);
// v.push_back(0);
for(int i = 0; i < tot; ++i) {
if(okrow(i)) {
v.push_back(i);
// cout << i << endl;
}
}
for(int i = 1; i <= n; ++i) {
for(int j = 0; j < v.size(); ++j) {
int t = v[j];
for(int k = 0; k < n; ++k) {
if((1<<k)&t) {
c[i][j] += a[i][k];
}
}
// cout << i <<" " << v[j] <<" " << c[i][j] << endl;
}
}
for(int i = 0; i < v.size(); ++i) {
dp[1][i] = c[1][i];
}
for(int i = 2; i <= n; ++i) {
for(int j = 0; j < v.size(); ++j) {
for(int k = 0; k < v.size(); ++k) {
if(ok(v[j],v[k])) {
// cout << j <<" " << k << endl;
// cout << i <<" " << j <<" " << k << " " << dp[i-1][k] << endl;
dp[i][j] = max(dp[i][j], dp[i-1][k]+c[i][j]);
// cout << i <<" " << v[j] <<" " <<" " << dp[i-1][k] <<" " << c[i][j] << " "<< dp[i][j] << endl;
}
}
}
}
//cout << v.size() << endl;
int ans = 0;
for(int i = 0; i < v.size(); ++i)
ans = max(ans, dp[n][i]);
cout << ans << endl;
}
return 0;
}