您的位置:首页 > 其它

[NOI2001][POJ1185]炮兵阵地(状压dp)

2016-10-31 23:34 519 查看

题目描述

传送门

题解

m很小并且山地和平原、放置与不放置可以用01表示,所以一看就是状压dp。

但是炮可以打到上下两个格子这一点很烦人,这样的话不能用前一行向当前行转移,因为有可能在前一行的前一行存在一个炮和当前行的冲突。

那么我们在状态表示里至少要存两行的状态。但是210∗2不是有点太大了么?有趣的是,每一行的炮与炮之间至少要留两个空格,所以每一行实际的方案数要比210要小很多,即使是最大的m=10也不过只有169个,这些合法的状态是可以预处理出来的。

设f(i,j,k)表示前i行,其中最后两行为第j个第k个状态的最大数量。然后就可以分别判断是否合法然后转移了。

理论时间复杂度是O(n(2m)3)。但是由于我们已经预处理了最多169个合法方案,中间各种判断又会砍掉很多冗余的状态,极限数据也是完全可以在2s内出解的。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 105

int n,m,tot,ans;
int a
,sol[N*2],num[N*2],f
[N*2][N*2];
char s
;

int calc(int x)
{
int ans=0;
while (x)
{
if (x&1) ans++;
x>>=1;
}
return ans;
}
int main()
{
scanf("%d%d\n",&n,&m);
for (int i=1;i<=n;++i)
{
gets(s);
for (int j=1;j<=m;++j)
if (s[j-1]=='P') a[i]+=(1<<(m-j));
}
for (int i=0;i<=(1<<m)-1;++i)
if (!(i&(i<<2))&&!(i&(i<<1)))
{
sol[++sol[0]]=i;
num[sol[0]]=calc(i);
}
if (n==1)
{
for (int i=1;i<=sol[0];++i)
if ((sol[i]&a[1])==sol[i]) ans=max(ans,num[i]);
printf("%d\n",ans);
return 0;
}
for (int i=1;i<=sol[0];++i)
if ((sol[i]&a[1])==sol[i])
for (int j=1;j<=sol[0];++j)
if ((sol[j]&a[2])==sol[j])
if (calc(sol[i]^sol[j])==num[i]+num[j])
f[2][i][j]=max(f[2][i][j],num[i]+num[j]);
for (int i=3;i<=n;++i)
for (int j=1;j<=sol[0];++j)
if ((sol[j]&a[i])==sol[j])
for (int k=1;k<=sol[0];++k)
if ((sol[k]&a[i-1])==sol[k])
for (int l=1;l<=sol[0];++l)
if ((sol[l]&a[i-2])==sol[l])
if (calc(sol[j]^sol[k])==num[j]+num[k]&&calc(sol[k]^sol[l])==num[k]+num[l]&&calc(sol[j]^sol[l])==num[j]+num[l])
f[i][k][j]=max(f[i][k][j],f[i-1][l][k]+num[j]);
for (int i=1;i<=sol[0];++i)
if ((sol[i]&a
)==sol[i])
for (int j=1;j<=sol[0];++j)
if ((sol[j]&a[n-1])==sol[j])
if (calc(sol[i]^sol[j])==num[i]+num[j])
ans=max(ans,f
[j][i]);
printf("%d\n",ans);
}


总结

①状压dp不要光考虑理论复杂度。合法状态有可能不是很多。

②状压dp某一个不要光考虑用一维表示某一个状态,其实也可以在预处理之后表示成第几个状态
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: