这题我几个月之前看了好久,没理解,今天又看一下午,茅塞顿开!!!
题意:
给行列的字符串矩阵,表示平原,表示山地,平原可以放炮兵,山地不可以放炮兵,问最多能够摆放多少炮兵。
思路:
首先,所以我们可以预处理长度为的(状态合法数组),又因为是合法摆放,合法状态并不是很多,数组大小开即可,按照炮兵的横向攻击范围,所有炮兵向左移动一位,或者向左移动两位都攻击与原数组的炮兵产生位置重叠,用位运算表示即答案为0,用表示这种状态下有多少个炮兵,为炮兵,给出代码:
for (int i = 0; i < (1 << m); i++)
{
if ((!(i & (i << 1))) && (!(i & (i << 2))))
stk[k] = i, np[k++] = getsum(i);
}
我们得到合法状态后,我们需要考虑,山地与炮兵的位置不能重合,所以我们需要把的位置用二进制串表示出来,累加即可,再二进制表示下,位是,下面给出代码:
for (int i = 1; i <= n; i++)
{
cin >> s;
for (int j = m - 1; j >= 0; j--)
if (s[j] == 'H')
sp[i] += (1 << j);
}
然后就是对进行考虑,代表第行的第状态,第行的状态最多可以放多少炮兵,所以我们需要初始化第一行可行状态的答案,下面给出代码:
for (int i = 1; i < k; i++)
{
if (!(sp[1] & stk[i]))
dp[1][i][1] = np[i];
}
动态转移方程:很容易得出
给出核心代码:
for (int i = 2; i <= n; i++)
{
for (int j = 1; j < k; j++)
{
if (sp[i] & stk[j])
continue;
for (int x = 1; x < k; x++)
{
if ((stk[x] & sp[i - 1]) || (stk[x] & stk[j]))
continue;
for (int y = 1; y < k; y++)
{
if ((stk[y] & stk[x]) || (stk[y] & stk[j]) || (stk[y] & sp[i - 2]))
continue;
dp[i][j][x] = max(dp[i][j][x], dp[i - 1][x][y] + np[j]);
}
}
}
}
代码详细版:
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
#include <set>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <math.h>
using namespace std;
typedef long long ll;
//#define ll long long
const ll N = 2e3 + 20;
const int maxn = 5e5 + 20;
const ll mod = 1000000007;
ll inv[maxn], vis[maxn], dis[maxn], head[maxn], dep[maxn], out[maxn];
ll fac[maxn], a[maxn], b[maxn], c[maxn], pre[maxn], cnt, sizx[maxn];
vector<ll> vec;
char s[maxn];
//typedef pair<ll, ll> p;
//priority_queue<p, vector<p>, greater<p> > m;
ll sum[maxn];
ll max(ll a, ll b) { return a > b ? a : b; }
ll min(ll a, ll b) { return a < b ? a : b; }
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
ll lcm(ll a, ll b) { return a * b / gcd(a, b); }
void swap(int &x, int &y) { x ^= y, y ^= x, x ^= y; }
map<ll, ll> mp;
ll ksm(ll a, ll b)
{
a %= mod;
ll ans = 1ll;
while (b)
{
if (b & 1)
ans = (ans * a) % mod;
a = (a * a) % mod;
b >>= 1ll;
}
return ans;
}
ll lowbit(ll x)
{
return x & (-x);
}
//int dx[105], dp[105], nc[105], w[105];
int dp[105][105][105];//存i行,第j状态,和上一行的k状态的最大炮兵数量
int stk[N], np[N], sp[105];//行合法状态,合法状态的炮兵个数,山地的分布01串
int getsum(int x)//求x的二进制状态中炮兵的数量
{
int ans = 0;
while (x)
{
if (x & 1)
ans++;
x >>= 1;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t, n, m, k;
// cin>>t;
// while(t--)
// {
string s;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> s;
for (int j = m - 1; j >= 0; j--)
if (s[j] == 'H')
sp[i] += (1 << j);//累加得每一行山地的分布串
}
// }
k = 1;
for (int i = 0; i < (1 << m); i++)//枚举长度为m的可行状态
{
if ((!(i & (i << 1))) && (!(i & (i << 2))))
stk[k] = i, np[k++] = getsum(i);
}
for (int i = 1; i < k; i++)//初始化dp[][][]第一行
{
if (!(sp[1] & stk[i]))
dp[1][i][1] = np[i];
}
for (int i = 2; i <= n; i++)
{
for (int j = 1; j < k; j++)
{
if (sp[i] & stk[j])//判断当前状态和山地是否重叠
continue;
for (int x = 1; x < k; x++)
{
if ((stk[x] & sp[i - 1]) || (stk[x] & stk[j]))//判断当前状态和山地和上一个状态是否有重叠
continue;
for (int y = 1; y < k; y++)
{
if ((stk[y] & stk[x]) || (stk[y] & stk[j]) || (stk[y] & sp[i - 2]))
continue;
//判断当前状态和山地和上一个状态,和上上一个状态是否有重叠
dp[i][j][x] = max(dp[i][j][x], dp[i - 1][x][y] + np[j]);
}
}
}
}
ll ans = 0;
//枚举第n层所有状态的最大值
for (int i = 1; i < k; i++)
{
for (int j = 1; j < k; j++)
{
ans =max(ans,dp[n][i][j]);
}
}
cout << ans << endl;
}