【题解】P2704 [NOI2001] 炮兵阵地
【题解】P2704 [NOI2001] 炮兵阵地
题目传送门
蒟蒻又来写模板题了别的也不会
这是一道经典状压Dp
题目大意
一个N\(\times\)M的01矩阵,选取若干个点\((x_i,y_i)\),满足任意两个点\((x_i,y_i),(x_j,y_j)\),都有若\(x_i=x_j\),则\(|y_i-y_j|>2\);若\(y_i=y_j\),则\(|x_i-x_j|>2\),且其点权为1。求最多能选取多少个点。
注意到数据范围"N<=100,M<=10",考虑状压dp
那么什么是状压dp呢?
在动态规划中,有时我们需要记录多个元素的状态(甚至元素的个数也不固定),若这些状态可以用0,1来表示(比如该元素是否选取),那么我们可以把这多个元素的状态压成一个二进制数,从而更加方便的进行状态的表示和转移
若这些元素的状态不能用0,1表示,也可以考虑用更高进制的数(但蒟蒻还不会嘤嘤嘤)
Solution
首先容易想到按行来划分“阶段”,一行一行的选取,发现每一行的选取只与前两行有关
那么设\(f_{i,j,k}\)表示前i行,其中第i行状态为j,第(i-1)行状态为k,使前i行的选取满足条件的最多选取点的数量
状态转移方程:
转移条件有:
除此之外,我们还要用一个count函数每次去计算j中1的个数来统计答案
如果我们每次转移都要像sb一样去重新算的话,复杂度直接爆炸
这时就要用我们的预处理大法啦
我们发现,在这些条件中,很多都可以提前预处理出来
条件3和4可以在dp之前,开一个数组S,把[1,1<<m)中符合条件的数存进去。经计算,当m取最大值10时,S的元素个数不超过100
条件5和6开一个fld数组,fld[i][j]表示S中第j个数中的每个1对应到原矩阵第i行时,是否都为1
至于count函数,对于S中的每个元素,开个sum数组统计即可
于是,我们就可以愉快的\(O(1)\)转移了!时间复杂度优化为\(O(N|S|_3)\),空间复杂度\((N|S|_2)\),若用滚动数组,则为\((|S|_2)\)
总结
- 在状压dp中,为了压缩转移时间,可考虑预处理一些数据。
也可以通过预处理合法的状态存到数组里,来减少枚举状态时的复杂度 - 要熟练掌握位运算啊啊啊啊啊啊
那就上Code咯
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
register int x=0,w=1;
register char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') {ch=getchar();w=-1;}
while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*w;
}
int n,m,s[100],tot;
int f[105][105][105];
bool fld[105][100];
int a[105][15];
int sum[105];
int main()
{
n=read();m=read();
char c;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
cin>>c;
a[i][j]=c=='P'?1:0;
}
for(int i=0;i<(1<<m);++i)
{
bool g=0;int cnt=2,num=0;
for(int j=0;j<m;++j)
{
if(i>>j&1) {
if(cnt<2){
g=1;break;
}
cnt=0;
++num;
}
else{
cnt++;
}
}
if(!g) s[++tot]=i,sum[tot]=num;
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=tot;++j)
{
// fld[i][j]=0;
for(int k=0;k<m;++k)
{
if(s[j]>>k&1!=a[i][k+1])
{
fld[i][j]=1;break;
}
}
}
}
memset(f,0xcf,sizeof f);
f[0][1][1]=0;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=tot;++j)
{
if(fld[i][j]) continue;
for(int k=1;k<=tot;++k)
{
if(s[j]&s[k]) continue;
if(fld[i-1][k]) continue; //剪枝,不加也可过
for(int l=1;l<=tot;++l)
{
if(s[j]&s[l]) continue;
f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+sum[j]);
}
}
}
}
int maxn=0;
for(int i=1;i<=tot;++i)
for(int j=1;j<=tot;++j)
maxn=max(maxn,f[n][i][j]);
cout<<maxn<<endl;
// for(int i=1;i<=tot;++i) cout<<s[i]<<" "<<sum[i]<<endl;
return 0;
}

浙公网安备 33010602011771号