【题解】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行的选取满足条件的最多选取点的数量

状态转移方程:

\[f_{i,j,k}=max\{f(i-1,k,l)+count(j)\} \]

转移条件有:

\[1. j&k==0 第i行与第i-1行不能在列数相同的地方取点 2. j&l==0 第i行与第i-2行不能在列数相同的地方取点 3. 在j这个二进制数中任意两个1之间的距离大于2 4. 在k这个二进制数中任意两个1之间的距离大于2 5. 在j这个二进制数中,任意一个1的位置对应到01矩阵中,其值都为1 6. 在k这个二进制数中,任意一个1的位置对应到01矩阵中,其值都为1 \]

除此之外,我们还要用一个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)\)

总结

  1. 在状压dp中,为了压缩转移时间,可考虑预处理一些数据。
    也可以通过预处理合法的状态存到数组里,来减少枚举状态时的复杂度
  2. 要熟练掌握位运算啊啊啊啊啊啊

那就上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;	
}
posted @ 2021-07-13 19:57  glq_C  阅读(74)  评论(0)    收藏  举报