天天看點

【洛谷P2704】【ssl1384】【NOI2001】炮兵陣地【狀壓DP】

Description

司令部的将軍們打算在NM的網格地圖上部署他們的炮兵部隊。一個NM的地圖由N行M列組成,地圖的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下圖。在每一格平原地形上最多可以布置一支炮兵部隊(山地上不能夠部署炮兵部隊);一支炮兵部隊在地圖上的攻擊範圍如圖中黑色區域所示:

如果在地圖中的灰色所辨別的平原上部署一支炮兵部隊,則圖中的黑色的網格表示它能夠攻擊到的區域:沿橫向左右各兩格,沿縱向上下各兩格。圖上其它白色網格均攻擊不到。從圖上可見炮兵的攻擊範圍不受地形的影響。

  現在,将軍們規劃如何部署炮兵部隊,在防止誤傷的前提下(保證任何兩支炮兵部隊之間不能互相攻擊,即任何一支炮兵部隊都不在其他支炮兵部隊的攻擊範圍内),在整個地圖區域内最多能夠擺放多少我軍的炮兵部隊。

Input

第一行包含兩個由空格分割開的正整數,分别表示N和M;

  接下來的N行,每一行含有連續的M個字元(‘P’或者‘H’),中間沒有空格。按順序表示地圖中每一行的資料。N≤100;M≤10。

Output

僅在第一行包含一個整數K,表示最多能擺放的炮兵部隊的數量。

Sample Input

5 4
PHPP
PPHH
PPPP
PHPP
PHHP
           

Sample Output

分析

看到資料很小,題目比較難,得出做法:狀壓DP

輸入高山平原的時候需要預處理,把地形轉化為01并壓縮到map數組裡面。

然後枚舉每一行的狀态,并且判斷這一行是否為合法狀态。判斷的方法就是在s變量(用于計算間隔)等于0之前又遇到一個1那就是不合法。如果等于0的時候遇到一個1那就把s變為3。s是每次-1的。

判斷合法的一行狀态就可以累加tot,表示真實用于DP的狀态的數量,可以有效壓縮數組空間。

然後開始DP。

我們設 f [ i ] [ l ] [ k ] f[i][l][k] f[i][l][k]為第 i i i行有 l l l點貢獻(放了 l l l個炮兵),上一行( i − 1 i-1 i−1行)有 k k k點貢獻。

狀态轉移方程: f [ i ] [ l ] [ k ] = m a x ( f [ i ] [ l ] [ k ] , f [ i − 1 ] [ k ] [ j ] + n u m [ l ] ) ; f[i][l][k]=max(f[i][l][k],f[i-1][k][j]+num[l]); f[i][l][k]=max(f[i][l][k],f[i−1][k][j]+num[l]);

這裡其實 j j j表示i-2行的貢獻,是以也要枚舉1層循環。

同時,不僅要判斷間隔,還要判斷是否為山地(map數組)。

上代碼

107行代碼47行括号。。。别問我為什麼那麼熱愛括号。。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
typedef long long ll;
using namespace std;

int n,m,map[1001],a[1<<10],num[1<<10],tot,ans;

int lowbit(int x)
{
    return x&(-x);	
}

bool check(int x)
{
	int s=0;
	while(x)//x這個二進制數已經表示了一行的狀态! 
	{
		if((x&1)&&s!=0)//間距沒到3又出現一個1,是以判斷為不合法狀态 
		{
			return false;
		}
		if(x&1)//如果這個值為1但是s減到0了,那證明間距是3或以上 
		{
			s=3;
		}
		if(s!=0)
		{
			s--;
		}
		x=x>>1;//一行中某一位判斷完畢 
	}
	return true;
}

int ask(int x)
{
	int s=0;
	for(;x>0;x-=lowbit(x))
	{
		s++;
	}
	return s;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
    	for(int j=1;j<=m;j++)
    	{
    		char x;
    		cin>>x;
    		if(x=='P')
    		{
    			map[i]=(map[i]<<1);//直接左移就是标0 
    		}
    		else
    		{
    			map[i]=(map[i]<<1)+1;//左移一位再加一就标上了1 
    		}
    	}
    }
    for(int i=0;i<=(1<<m)-1;i++)
	{
		if(check(i))
		{
			tot++;//算真實要用于DP的數字有多少,表示有哪幾行是合法的 
			a[tot]=i;
			num[tot]=ask(i);
		}
	} 
	int f[110][tot+1][tot+1];//f數組開101直接WA,要開大一點!!!! 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=tot;j++)
		{
			if((a[j]&map[i-2])==0)
			{
				for(int k=1;k<=tot;k++)
				{
					if((a[j]&a[k])==0&&(a[k]&map[i-1])==0)
					{
						for(int l=1;l<=tot;l++)
						{
							if((a[j]&a[l])==0&&(a[k]&a[l])==0&&(a[l]&map[i])==0)
							{
								f[i][l][k]=max(f[i][l][k],f[i-1][k][j]+num[l]);
							}
						}
					}
				}
			}
		}
	} 
	for(int i=1;i<=tot;i++)//目前行的貢獻(炮兵數量) 
	{
		for(int j=1;j<=tot;j++)//上一行的貢獻 
		{
			ans=max(ans,f[n][i][j]);//n就是DP枚舉到第n行 
		}
	}
	cout<<ans; 
	return 0;
}
           

括号永遠滴神!!!