🎁题目: 炮兵阵地
思路:
人话翻译一下就是给定一个N*M的矩阵,标有的位置不能放置大炮,一个旗子的上下左右四个方向,两格以内不能放置棋子。现在求可以放置棋子的最大个数。
数据范围是 N <= 100,M <=10,M是列数,N是行号。故可以考虑二进制压缩状态,把第i行的摆放状态进行状态压缩——“线性状压DP”
但是根据本题题意,第i行要考虑第i-1行和第i-2行的状态,如果定义状态的时候,只压缩当前层的状态进行转移,是不能保证本次转移是合法的。我们还需要知道前一行的状态,并根据前一行的状态和本行的状态来确定第i-2行的状态。
状态定义:
f(i,j,k)——表示当前在第i行,且第i - 1行的状态是 j ,第 i 行的状态是 k 的最大摆放数量。
那么前一个状态就表示为 f (i - 1,第 i - 2行的状态,j),可以看出,我们根据第 i - 2行的状态来转移。
f( i, j, k ) = max( f(i ,j ,k ), f( i - 1, 第 i - 2行的状态, j ) + 本行摆放大炮的个数 );
首先需要预处理一下所有的合法状态(仅考虑本行):
需要满足 (我们设k是本次枚举的状态)—— k && k >> i & 1 || k >> i + 1 & 1
即,本行两个格子范围内只能出现一个大炮。
注:用二进制数来表示所有状态,假设M = 10,即列号等于10,我们就可以用一个 [1,1 << 10)
中的所有数来表示所有状态,每一个数对应唯一的一个01串。如:1010100011(是1的地方就放
了大炮)
bool check(int k)
{
for(int i = 0;i < m;i ++ )
if( (k >> i & 1) && (k >> i + 1 & 1) | (k >> i + 2 & 1) )
return false;
return true;
}
for(int i = 0;i < 1 << m;i ++ )
if(check(i))
{
states.push_back(i);
cnt[i] = count(i);
}
合法转移:
1:需要满足第 i 行的第 j 个位置如果是 1 ,那么在 第i - 2行 和 i - 1行 都要是 0;
即:(a & b ) | (a & c) | (b & c) == 0
2:需要 所有 大炮不摆放在 H 上,设b为第 i 行的状态,c为第 I - 1行的状态
即:(g[i] & c) | (g[i - 1] & b) == 0
注:(g[i]中预存了第 i 行 H的位置)
for(int i = 1;i <= n;i ++ )
for(int j = m - 1; j >= 0; j -- )//其实反了也没关系
{
char c;
cin >> c;
if(c == 'H')//表示是山地,不能部署大炮
{
g[i] += 1 << j;
}
}
for(int i = 1;i <= n + 2 ;i ++ )
for(int j = 0;j < states.size(); j ++ )
for(int k = 0;k < states.size(); k ++ )
for(int u = 0;u < states.size(); u ++)
{
int a = states[j],b = states[k],c = states[u];
if((a & b) | (a & c) | (b & c))continue;
if(g[i] & c | g[i - 1] & b )continue;
f[i & 1][k][u] = max(f[i & 1][k][u],f[i - 1 & 1][j][k] + cnt[c]);
}
滚动数组优化:
由于本题的M = 1<<10,已经很大了,如果不用滚动数组优化的话会内存超标。
只需要在转移的时候把第一维的 参数 & 1,就可以滚起来了。这样的话第一维只用开两个
注:奇数 & 1 == 1,偶数 & 1 == 0,不是奇数就是偶数
代码详解:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N = 110,M = 12,K = 1 << 10;
int f[2][K][K];
int g[N];
int n,m;
int cnt[K];
vector<int> states;
bool check(int k)
{
for(int i = 0;i < m;i ++ )
if( (k >> i & 1) && (k >> i + 1 & 1) | (k >> i + 2 & 1) )
return false;
return true;
}
int count(int k)
{
int res = 0;
for(int i = 0;i < m;i ++)
res += k >> i & 1;
return res;
}
int main()
{
cin >> n >> m;
for(int i = 1;i <= n;i ++ )
for(int j = m - 1; j >= 0; j -- )//其实反了也没关系
{
char c;
cin >> c;
if(c == 'H')//表示是山地,不能部署大炮
{
g[i] += 1 << j;
}
}
//预处理所有的合法状态(只考虑本行的情况下)
for(int i = 0;i < 1 << m;i ++ )
if(check(i))
{
states.push_back(i);
cnt[i] = count(i);
}
for(int i = 1;i <= n + 2 ;i ++ )
for(int j = 0;j < states.size(); j ++ )
for(int k = 0;k < states.size(); k ++ )
for(int u = 0;u < states.size(); u ++)
{
int a = states[j],b = states[k],c = states[u];
if((a & b) | (a & c) | (b & c))continue;
if(g[i] & c | g[i - 1] & b )continue;
f[i & 1][k][u] = max(f[i & 1][k][u],f[i - 1 & 1][j][k] + cnt[c]);
}
/*int res = 0;
for(int i = 0;i < states.size(); i ++)
for(int j = 0;j < states.size(); j ++ )
res = max(res,f[n & 1][i][j]);
cout << res;
*/
cout << f[n + 2 & 1][0][0];
return 0;
}
最后输出的时候也有一个小技巧,可以直接输出f [ n + 2 ][0][0]的值即是要求的答案,就相当于第n + 2行且 第 n + 2 行放了 0 个大炮,第 n - 1 行放了 0 个大炮,那么此状态就囊括了所有第 n 行的状态。
我是一只无情的AC机器~