HDU4539(狀態壓縮動態規划)


原題鏈接

算法解析

首先觀察數據范圍

我們發現,\(n \le 10\)

這是狀態壓縮DP的典型數據范圍

接着我們看本題是一個棋盤,然后一個點的放置受到其他點的限制

那么我們可以確定本題為棋盤類型的狀態壓縮

顯然每一行的狀態是必須儲存下來的

問題是,這里有m行,那么這么多數據,我們難道要全部壓縮進來嗎?

完全不能!

那么我們來觀察一個點的狀態限制

攻擊狀態.png

紅色是我們當前放置的點,而橙色是我們不能放置的點。

根據上面的圖片顯示,一個點在不考慮下面擺放的前提下,我們只需要考慮上面兩行的狀態。

我們不妨設f[2][a][b]表示當前行,狀態為a,上一行的狀態為b

那么在這里,我們狀態轉移就是

f[i][a][b]=max(f[i][a][b],f[i-1][b][c]+cnt[a])

在這里cnt[a]表示狀態為a,放置了多少個士兵

接下來我們需要考慮狀態限制的具體表示

  1. 放置士兵的格子,左邊2格,右邊2格上不能有士兵
  2. 放置士兵的格子,上面一行,左邊1格,右邊1格不能放置士兵
  3. 放置士兵的格子,上上行不能放置士兵

然后一個狀態,有10位,那么存放大小得為1024

但是根據同一行,左右2格上不能放置的規定(也就是第一條)

我們發現最后合法的狀態,一共有169個狀態

所以我們不妨把這些合法狀態編號,然后用編號來表示對應的狀態

這樣可以壓縮狀態,保證不超內存

最后記得一點:循環枚舉時候,遇到不合法的狀態,直接跳過,不要浪費時間。(具體看代碼實現)

易錯點總結:

  1. 沒有考慮上上行的狀態
  2. 狀態沒有壓縮好,導致MLE

代碼解析

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
int f[2][220][220],cnt[210],n,m,pre[110],ok[220],qs;//其實可以用滾動數組
inline int check1(int a)
{
	bool ok1=a & (a<<2);//判斷a有沒有自己打自己的
	return ok1;
}
inline int check2(int a,int b)//a為這一行狀態,b為上一行狀態
{
	bool ok1=b & (a<<1);//來自右邊的
	bool ok2=b & (a>>1);//來自左邊的
	return (ok1 | ok2);
}
int count(int x)//統計這種狀態下,放置點的數量
{
	int ans=0;
	while(x)
	{
		ans+=(x&1);
		x>>=1;
	}
	return ans;
}
inline void work()
{
	int ans=0;
	for(int i=1; i<=m; i++)
	{
		for(int s=0 ; s<qs; s++)//當前行
		{
			int now=ok[s];
			if (now & pre[i])//自判 &子集判斷
				continue;
			for(int j=0; j<qs; j++)
			{
				int last=ok[j];
				if (last & pre[i-1])//判斷狀態是否自身合法,可以放置
					continue;
				if (check2(now,last))//上下對打
					continue;
				for(int k=0; k<qs; k++)
				{
					int kk=ok[k];
					if (kk & pre[i-2])//判斷狀態是否自身合法,可以放置
						continue;
					if (check2(last,kk))
						continue;
					if (now & kk)//自己和上上對打,很重要
						continue;
					f[i & 1][s][j]=max(f[i & 1][s][j],f[i-1 & 1][j][k]+cnt[s]);
					//f[i][j][k]當前行為i,當前行狀態的代號為j,上一行狀態的代號為k
				}
			}
		}
	}
	for(int i=0; i<qs; i++)
		for(int j=0; j<qs; j++)
			ans=max(ans,f[m & 1][i][j]);
	printf("%d\n",ans);
}
inline void init()
{
	while(scanf("%d%d",&m,&n)!=EOF)
	{
		memset(f,0,sizeof(f));
		memset(pre,0,sizeof(pre));
		qs=0;
		for(int i=0; i<1<<n; i++)
			if(!check1(i))//找到滿足自己不打自己的狀態們
				ok[qs]=i,cnt[qs]=count(i),++qs;//統計子集狀態
		int now=0;
		for(int i=1; i<=m; i++)
			for(int j=0; j<n; j++)
			{
				scanf("%d",&now);
				if (now==0)
					pre[i]+=(1<<j);//構造不可以放士兵的狀態
			}
		work();
	}
}
signed main()
{
	init();
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM