题意:
给出一个地图,包含三种格子的地图,'O'表示必须走的点,'*'表示可以走的点,'X'表示不能走到的点。问走完必须走的点的回路个数
题解:
这题很明显是单回路问题,但是格子有三种,因此就无法确定左后一个非障碍位置,因为我们引入一个变量标记是否某个状态是否已经形成回路。具体做法:每次将状态解压出来时都要让标记变量赋值为状态的最高位(最高位存的就是否是已经有回路的标记),每次在装以前要判断是否已经有回路,有回路要特殊判断转移方程。
#include<iostream>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<map>
using namespace std;
typedef __int64 lld;
#define oo 0x3f3f3f3f
#define OO 0x3f3f3f3f3f3f3f3f
#define HASH 10007
#define STATE 100010
#define MAXD 15
int N,M;
int code[MAXD],maze[MAXD][MAXD];
int ch[MAXD];
int IsEnd;
struct HASHMAP
{
int head[HASH],next[STATE],sizes;
lld dp[STATE],state[STATE];
void init()
{
sizes=0;
memset(head,-1,sizeof head);
}
void push(lld st,lld ans)
{
int h=st%HASH;
for(int i=head[h];i!=-1;i=next[i])
{
if(st==state[i])
{
dp[i]+=ans;
return ;
}
}
dp[sizes]=ans;
state[sizes]=st;
next[sizes]=head[h];
head[h]=sizes++;
}
}hm[2];
void decode(int code[],int m,lld st)
{
for(int i=m;i>=0;i--)
{
code[i]=st&7;
st>>=3;
}
IsEnd=st&1;//相当于0——m中0的那位数组作为标记是否已经形成环
}
lld encode(int code[],int m)//最小表示法
{
lld st=IsEnd;
int cnt=0;
memset(ch,-1,sizeof ch);
ch[0]=0;
for(int i=0;i<=m;i++)
{
if(ch[code[i]]==-1) ch[code[i]]=++cnt;
code[i]=ch[code[i]];
st<<=3;
st|=code[i];
}
return st;
}
void shift(int code[],int m)///换行 移位
{
for(int i=m;i>0;i--)
code[i]=code[i-1];
code[0]=0;
}
void dpblank(int i,int j,int cur)
{
int left,up;
for(int k=0;k<hm[cur].sizes;k++)
{
decode(code,M,hm[cur].state[k]);
left=code[j-1];
up=code[j];
if(IsEnd)
{
///如果已经形成环路,后面有插头或者必须经过的点说明这个方案是不合法的删去
if(left||up||maze[i][j]==2)
continue;
code[j-1]=code[j]=0;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
continue;
}
if(left&&up)///11 -> 00 有上插头和左插头,这种情况下相当于合并两个连通分量
{
if(left==up)///如果在同一个联通分量
{
code[j-1]=code[j]=0;
IsEnd=1;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
else///不再同一个联通分量里面可以进行合并
{
code[j-1]=code[j]=0;
for(int t=0;t<=M;t++)
if(code[t]==up)
code[t]=left;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
}
else if(left||up)///01 || 10 上插头和左插头恰好有一个,这种情况相当于延续原来的连通分量
{
int temp;
if(left) temp=left;
else temp=up;
if(maze[i][j+1])
{
code[j-1]=0;
code[j]=temp;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
if(maze[i+1][j])
{
code[j-1]=temp;
code[j]=0;
if(j==M)shift(code,M);///切记不可忘记,换行要shift
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
}
else///没有上插头和左插头,有下插头和右插头,相当于构成一个新的连通块
{
if(maze[i][j+1]&&maze[i+1][j])
{
code[j]=code[j-1]=13;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
if(maze[i][j]==1)
{
code[j]=code[j-1]=0;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
}
}
}
void dpblock(int i,int j,int cur)
{
for(int k=0;k<hm[cur].sizes;k++)
{
decode(code,M,hm[cur].state[k]);
code[j-1]=code[j]=0;///因为有障碍物所以 左插头j-1 和 上插头j 都消失了(就是不联通了)
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
}
void init()
{
memset(maze,0,sizeof maze);
char c;
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++)
{
getchar();
for(int j=1;j<=M;j++)
{
scanf("%c",&c);
if(c=='*')
maze[i][j]=1;
else if(c=='O')
maze[i][j]=2;
}
}
}
void solve()
{
int cur=0;
lld ans=0;
hm[cur].init();
hm[cur].push(0,1);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
{
hm[cur^1].init();
if(maze[i][j]) dpblank(i,j,cur);
else dpblock(i,j,cur);
cur^=1;
}
for(int i=0;i<hm[cur].sizes;i++)
ans+=hm[cur].dp[i];
printf("%I64d\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
for(int cas=1;cas<=T;cas++)
{
init();
printf("Case %d: ",cas);
solve();
}
return 0;
}
/**
2
2 2
OO
O*
4 4
***O
XO**
**O*
XX**
*/