算法解析
首先觀察數據范圍
我們發現,\(n \le 10\)
這是狀態壓縮DP的典型數據范圍
接着我們看本題是一個棋盤,然后一個點的放置受到其他點的限制。
那么我們可以確定本題為棋盤類型的狀態壓縮
顯然每一行的狀態是必須儲存下來的
問題是,這里有m
行,那么這么多數據,我們難道要全部壓縮進來嗎?
完全不能!
那么我們來觀察一個點的狀態限制
紅色是我們當前放置的點,而橙色是我們不能放置的點。
根據上面的圖片顯示,一個點在不考慮下面擺放的前提下,我們只需要考慮上面兩行的狀態。
我們不妨設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,放置了多少個士兵
接下來我們需要考慮狀態限制的具體表示:
- 放置士兵的格子,左邊
2
格,右邊2
格上不能有士兵 - 放置士兵的格子,上面一行,左邊
1
格,右邊1
格不能放置士兵 - 放置士兵的格子,上上行不能放置士兵
然后一個狀態,有10
位,那么存放大小得為1024
但是根據同一行,左右2
格上不能放置的規定(也就是第一條)
我們發現最后合法的狀態,一共有169
個狀態
所以我們不妨把這些合法狀態編號,然后用編號來表示對應的狀態
這樣可以壓縮狀態,保證不超內存
最后記得一點:循環枚舉時候,遇到不合法的狀態,直接跳過,不要浪費時間。(具體看代碼實現)
易錯點總結:
- 沒有考慮上上行的狀態
- 狀態沒有壓縮好,導致
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;
}