【POJ1185】【洛谷P2704】炮兵阵地【状压dp】
题目大意:
题目链接:
POJ:http://poj.org/problem?id=1185
洛谷:https://www.luogu.org/problemnew/show/P2704
在一个的网格中,有些格子可以选择,任意两个选择的点不可以在同一直线的距离小于3。求最大的选择方案。
思路:
,可以考虑状压。
由于对于一个点选择或者不选会影响到下面两行的选择,所以我们可以设表示第行,这一行和上一行的放置方案为和的方案数。设上上行的状态为,则有
其中表示状态为放置的个数(即状态为中数字1的个数)
但是这样的时间复杂度为,空间复杂度为,TLE+MLE两开花(TM两开花XD)。
我们会发现枚举时有太多状态是不成立的。也就是说这个状态中就有两个1之间的距离小于3,。所以我们可以预处理出所有的合法的状态(即任意两个1之间的距离至少为3),然后直接枚举这些状态(顺便预处理出数组)。
这样可以省去超级多的无用状态,大大减少时间复杂度。
然后我们为了减少空间,可以设表示第行,这一行的状态是第j个合法的状态,上一行的状态是第k个合法的状态
时的最多放置方案数。方程没有怎么变。
这样就可以过掉本题了。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N=110;
const int MAXN=(1<<10);
int n,m,maxn,map[N],q[MAXN],sum[MAXN],tot,ans;
char ch;
bool check(int x) //判断这个状态是否合法
{
int cnt=0,sum;
while (x)
{
if ((x&1)&&cnt) return 0;
if (x&1) cnt=3;
if (cnt) cnt--;
x>>=1;
}
return 1;
}
int count(int x) //计算这个撞他中有几个1(您强可以使用lowbit)
{
int cnt=0;
while (x)
{
cnt+=(x&1);
x>>=1;
}
return cnt;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
cin>>ch;
map[i]=(map[i]<<1)+(ch=='H'); //压缩这一行的那些位置可以放
}
maxn=(1<<m);
for (int S=0;S<maxn;S++) //预处理
if (check(S))
{
q[++tot]=S;
sum[tot]=count(S);
}
int f[N][tot+1][tot+1]; //压缩空间复杂度
memset(f,0,sizeof(f));
for (int i=1;i<=n;i++)
for (int j=1;j<=tot;j++)
if (i==1||!(q[j]&map[i-2]))
for (int k=1;k<=tot;k++)
if (!(q[k]&map[i-1])&&!(q[j]&q[k]))
for (int l=1;l<=tot;l++)
if (!(q[l]&map[i])&&!(q[j]&q[l])&&!(q[k]&q[l]))
f[i][l][k]=max(f[i][l][k],f[i-1][k][j]+sum[l]);
for (int i=1;i<=tot;i++)
for (int j=1;j<=tot;j++)
ans=max(ans,f[n][i][j]);
printf("%d",ans);
return 0;
}