题意:用1×2的骨牌来铺带障碍格的矩形网格,使得每个网格至多被一张骨牌覆盖且无法再加入骨牌(即没有相邻的未覆盖无障碍网格)。求需要的最少骨牌数。
分析:轮廓线dp。每个格子的状态用一个4进制数来表示,0代表未覆盖,1代表已覆盖且由骨牌右端或下端覆盖,2代表已覆盖且由骨牌左端覆盖,3代表已覆盖且由骨牌上端覆盖。转移按如下分类进行:
1.障碍格:将当前格状态改成1
若不是情形1
2.左格(存在)状态为2或上格状态为3:将当前格状态改成1
若不是情形1且不是情形2
3.考虑当前格状态是否可为2或3
4.考虑当前格是否可以不放骨牌
代码
#include<bits/stdc++.h>
using namespace std;
const int maxS=1<<20;
struct node
{
vector<int> sta,f;
int mp[maxS];
void clear()
{
for (int i=0;i<sta.size();i++)
{
int S=sta[i];
mp[S]=-1;
}
sta.clear();f.clear();
}
void updata(int S,int f0)
{
int k=mp[S];
if (k!=-1) f[k]=min(f[k],f0);
else
{
sta.push_back(S);
f.push_back(f0);
k=sta.size();
mp[S]=k-1;
}
}
}nd[2];
int n,m,one[20],two[20],three[20];
char ch[200][20];
void init()
{
for (int i=0;i<2;i++) memset(nd[i].mp,-1,sizeof(nd[i].mp));
for (int i=1;i<=10;i++) one[i]=1<<(2*i-2),two[i]=1<<(2*i-1),three[i]=one[i]^two[i];
}
int get(int S,int j)
{
return (S&three[j])>>(2*j-2);
}
int make(int S,int j,int p)
{
S|=three[j];
if (p==0) return S^three[j];
if (p==1) return S^two[j];
if (p==2) return S^one[j];
return S;
}
int main()
{
init();
int T;cin>>T;
while (T--)
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%s",ch[i]+1);
int S0=0,pre=0,now=1;
for (int i=1;i<=m;i++) S0^=one[i];
nd[now].clear();nd[now].updata(S0,0);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
swap(pre,now);nd[now].clear();
for (int k=0;k<nd[pre].sta.size();k++)
{
int S=nd[pre].sta[k],f=nd[pre].f[k];
if (ch[i][j]=='*')
{
int S1=make(S,j,1);
nd[now].updata(S1,f);
}
else
{
bool flag=0;
if (j>1&&get(S,j-1)==2)
{
flag=1;
int S1=make(S,j,1);
nd[now].updata(S1,f);
}
if (get(S,j)==3)
{
flag=1;
int S1=make(S,j,1);
nd[now].updata(S1,f);
}
if (flag) continue;
if (j<m&&ch[i][j+1]=='.'&&get(S,j+1)!=3)
{
int S1=make(S,j,2);
nd[now].updata(S1,f+1);
}
if (i<n&&ch[i+1][j]=='.')
{
int S1=make(S,j,3);
nd[now].updata(S1,f+1);
}
if ((j==1||get(S,j-1))&&get(S,j))
{
int S1=make(S,j,0);
nd[now].updata(S1,f);
}
}
}
}
int ans=n*m;
for (int k=0;k<nd[now].f.size();k++) ans=min(ans,nd[now].f[k]);
printf("%d\n",ans);
}
return 0;
}