题意:
一个n*m的地图,有三种类型的格子,1表示障碍物,0表示空,2、3分别表示不同类型的,现在要将2和2,3和3相连(都只有一对),当然不能交叉,求最短的和。
题解:
这是另外一类的插头dp问题,多路径的问题。应为插头类型较多所以考虑用多进制表示,这里有4格子,因此考虑用3进制表示法做,但是由于4进制比较高效,因此用四进制做。题解附了注释,应该很详细了。
#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 1000010
#define MAXD 15
int N,M,ex,ey;
int code[MAXD];
int maze[MAXD][MAXD];
struct HASHMAP
{
int head[HASH],next[STATE],sizes;
int dp[STATE];
int state[STATE];
void init()
{
sizes=0;
memset(head,-1,sizeof head);
}
void push(int st,int ans)
{
int h=st%HASH;
for(int i=head[h];i!=-1;i=next[i])
{
if(st==state[i])
{
if(ans<dp[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&3;
st>>=2;
}
}
int encode(int code[],int m)///四进制表示法
{
int st=0;
for(int i=0;i<=m;i++)
{
st<<=2;
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(left&&up)/// 有上插头和左插头,这种情况下相当于合并两个连通分量
{
if(left==up)///插头要一样才能合并
{
code[j-1]=code[j]=0;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
}
else if(left||up)/// 上插头和左插头恰好有一个,这种情况相当于延续原来的连通分量
{
int temp;
if(left) temp=left;
else temp=up;
if(maze[i][j+1]==1||maze[i][j+1]==temp)///从右插头出来
{
code[j-1]=0;
code[j]=temp;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
if(maze[i+1][j]==1||maze[i+1][j]==temp)///从下面出来
{
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]+1);
}
}
else///没有上插头和左插头,有下插头和右插头,相当于构成一个新的连通块
{
///不加插头
code[j]=code[j-1]=0;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
///加插头
if(maze[i][j+1]&&maze[i+1][j])
{
if(maze[i][j+1]==1&&maze[i+1][j]==1)///可以加两种插头
{
decode(code,M,hm[cur].state[k]);
//加2号插头
code[j-1]=code[j]=2;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
//加3号插头
code[j-1]=code[j]=3;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
else if((maze[i][j+1]==1&&maze[i+1][j]==2)||(maze[i][j+1]==2&&maze[i+1][j]==1)||(maze[i][j+1]==2&&maze[i+1][j]==2))
{
decode(code,M,hm[cur].state[k]);
code[j-1]=code[j]=2;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
else if((maze[i][j+1]==1&&maze[i+1][j]==3)||(maze[i][j+1]==3&&maze[i+1][j]==1)||(maze[i][j+1]==3&&maze[i+1][j]==3))
{
decode(code,M,hm[cur].state[k]);
code[j-1]=code[j]=3;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
}
}
}
}
void dpblock(int i,int j,int cur)
{
for(int k=0;k<hm[cur].sizes;k++)
{
decode(code,M,hm[cur].state[k]);
if(code[j-1]!=0||code[j]!=0)continue;///不能有插头,因为是障碍物,有插头进来的方案肯定行不通啊!!!
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]);
}
}
///封成一个函数通过num判断是几号插头的dp
void dp_num(int i,int j,int cur,int num)
{
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((left==num&&up==0)||(left==0&&up==num))
{
code[j-1]=code[j]=0;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
else if(left==0&&up==0)
{
if(maze[i][j+1]==1||maze[i][j+1]==num)
{
code[j-1]=0;
code[j]=num;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
if(maze[i+1][j]==1||maze[i+1][j]==num)
{
code[j-1]=num;
code[j]=0;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+1);
}
}
}
}
void init()
{
memset(maze,0,sizeof maze);
int temp;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
scanf("%d",&temp);
if(temp==1)
maze[i][j]=0;
else if(temp==0)
maze[i][j]=1;
else
maze[i][j]=temp;
}
}
}
void solve()
{
int cur=0;
int ans=0;
hm[cur].init();
hm[cur].push(0,0);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
{
hm[cur^1].init();
if(maze[i][j]==0)dpblock(i,j,cur);
else if(maze[i][j]==1)dpblank(i,j,cur);
else dp_num(i,j,cur,maze[i][j]);
cur^=1;
}
for(int i=0;i<hm[cur].sizes;i++)
ans+=hm[cur].dp[i];
if(ans>0) ans-=2;///多计算了两个的头
printf("%d\n",ans);
}
int main()
{
while(scanf("%d %d",&N,&M)!=EOF)
{
if(N==0&&M==0)break;
init();
solve();
}
return 0;
}
/**
5 5
0 0 0 0 0
0 0 0 3 0
2 0 2 0 0
1 0 1 1 1
0 0 0 0 3
2 3
2 2 0
0 3 3
6 5
2 0 0 0 0
0 3 0 0 0
0 0 0 0 0
1 1 1 0 0
0 0 0 0 0
0 0 2 3 0
5 9
0 0 0 0 0 0 0 0 0
0 0 0 0 3 0 0 0 0
0 2 0 0 0 0 0 2 0
0 0 0 0 3 0 0 0 0
0 0 0 0 0 0 0 0 0
9 9
3 0 0 0 0 0 0 0 2
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 3
9 9
0 0 0 1 0 0 0 0 0
0 2 0 1 0 0 0 0 3
0 0 0 1 0 0 0 0 2
0 0 0 1 0 0 0 0 3
0 0 0 1 1 1 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
9 9
0 0 0 0 0 0 0 0 0
0 3 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 2 3 2
0 0
*/