POJ 3133 Manhattan Wiring(限制匹配的插头DP)

34 篇文章 1 订阅

Manhattan Wiring
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 1921 Accepted: 1130

Description

There is a rectangular area containing n × m cells. Two cells are marked with “2”, and another two with “3”. Some cells are occupied by obstacles. You should connect the two “2”s and also the two “3”s with non-intersecting lines. Lines can run only vertically or horizontally connecting centers of cells without obstacles.

Lines cannot run on a cell with an obstacle. Only one line can run on a cell at most once. Hence, a line cannot intersect with the other line, nor with itself. Under these constraints, the total length of the two lines should be minimized. The length of a line is defined as the number of cell borders it passes. In particular, a line connecting cells sharing their border has length 1.

Fig. 1(a) shows an example setting. Fig. 1(b) shows two lines satisfying the constraints above with minimum total length 18.

Figure 1: An example of setting and its solution

Input

The input consists of multiple datasets, each in the following format.

nm
row1
rown

n is the number of rows which satisfies 2 ≤ n ≤ 9. m is the number of columns which satisfies 2 ≤ m ≤ 9. Each rowi is a sequence of m digits separated by a space. The digits mean the following.

0: Empty

1: Occupied by an obstacle

2: Marked with “2”

3: Marked with “3”

The end of the input is indicated with a line containing two zeros separated by a space.

Output

For each dataset, one line containing the minimum total length of the two lines should be output. If there is no pair of lines satisfying the requirement, answer “0” instead. No other characters should be contained in the output.

Sample Input

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

Sample Output

18
2
17
12
0
52
43

Source


题目大意:

    在一个n*m的矩阵上又一些点能走,有一些点是障碍,有两个编号为2的点,两个编号为3的点,问把编号为2的点连起来所需的最短路径和,编号为3的点连起来。要求一个位置只能经过一次。


解题思路:

    矩阵上找路径,自然想到插头dp。不过这里添加了匹配的限制条件,所以路径就有两种,把经过点2的路径叫做路径2,经过点3的路径叫做路径3,由于题目要求的是最短路径,我们在dp的过程中就不需要用最小表示法了,直接用路径的编号表示插头即可。这样虽然会因为没有维护连通性导致出现多余的环,但是不会影响答案。


AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <vector>
#include <queue>
#include <stack>
#include <deque>
#include <string>
#include <map>
#include <set>
#include <list>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define fi first
#define se second
#define mem(a,b) memset((a),(b),sizeof(a))

const int MAXN=15;
int N, M, maze[MAXN][MAXN];//0不能走,1可以走,2位标记为2的点,3为标记为3的点
int tmp_state[MAXN];//保存临时轮廓线状态

struct HashMap  
{  
    const static int mod=10007;//小一点  
    const static int maxn=1000100;//大概状态数大一些  
    int head[mod];//链表头指针  
    int next[maxn];//指向链表下一个节点  
    int size;//当前节点数  
    int key[maxn],val[maxn];//键,值  
    void clear()  
    {  
        size=0;  
        memset(head,-1,sizeof head);  
    }  
    inline void insert(int _key,int _val)  
    {  
        int p=_key%mod;//取模后对应的链  
        for(int i=head[p];~i;i=next[i])  
            if(key[i]==_key)  
            {  
                val[i]=min(val[i], _val);//找到相同键值的结点后的具体操作根据题意修改  
                return ;  
            }  
        key[size]=_key;  
        val[size]=_val;  
        next[size]=head[p];  
        head[p]=size++;  
    }  
}hm[2]; 

void decode(int *tmp_state, int m, int key)//把数字解压到数组,M表示矩阵一行的长度
{
    for(int i=m;i>=0;--i)//这里用2位表示一个插头的状态
    {
        tmp_state[i]=key&3;
        key>>=2;
    }
}

int encode(int *tmp_state, int m)//把状态压缩到一个数字
{
    int res=0;
    for(int i=0;i<=m;++i)
    {
        res<<=2;
        res|=tmp_state[i];
    }
    return res;
}

void shift(int *tmp_state, int m)//处理完一行时,把竖着的轮廓线转移到下一行
{
    for(int i=m;i>0;--i)
        tmp_state[i]=tmp_state[i-1];
    tmp_state[0]=0;
}

void dp0(int y, int x, bool now)//对不能走的点dp
{
    for(int i=0;i<hm[now].size;++i)
    {
        decode(tmp_state, M, hm[now].key[i]);
        if(!tmp_state[x-1] && !tmp_state[x])
        {
            if(x==M)
                shift(tmp_state, M);
            hm[!now].insert(encode(tmp_state, M), hm[now].val[i]);
        }
    }
}

void dp1(int y, int x, int now)//对可以走的点dp
{
    for(int i=0;i<hm[now].size;++i)
    {
        decode(tmp_state, M, hm[now].key[i]);
        int left=tmp_state[x-1], up=tmp_state[x];
        if(left && up)//两个插头
        {
            if(left==up)//必须两个插头类型一致才能dp
            {
                tmp_state[x-1]=tmp_state[x]=0;
                if(x==M)
                    shift(tmp_state, M);
                hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
            }
        }
        else if((!left && up) || (left && !up))//只有一个插头
        {
            int t=left?left:up;
            if(maze[y][x+1]==1 || maze[y][x+1]==t)//向右延伸
            {
                tmp_state[x-1]=0;
                tmp_state[x]=t;
                hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
            }
            if(maze[y+1][x]==1 || maze[y+1][x]==t)//向下延伸
            {
                tmp_state[x-1]=t;
                tmp_state[x]=0;
                if(x==M)
                    shift(tmp_state, M);
                hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
            }
        }
        else//没有插头
        {
            if(x==M)
                shift(tmp_state, M);
            hm[!now].insert(encode(tmp_state, M), hm[now].val[i]);
            if(maze[y][x+1] && maze[y+1][x])
            {
                if(maze[y][x+1]==1 && maze[y+1][x]==1)
                {
                    tmp_state[x-1]=tmp_state[x]=2;//加2号插头
                    hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
                    tmp_state[x-1]=tmp_state[x]=3;//加3号插头
                    hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
                }
                else if((maze[y][x+1]==1 && maze[y+1][x]==2) || (maze[y][x+1]==2 && maze[y+1][x]==1) || (maze[y][x+1]==2 && maze[y+1][x]==2))
                {
                    tmp_state[x-1]=tmp_state[x]=2;
                    hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
                }
                else if((maze[y][x+1]==1 && maze[y+1][x]==3) || (maze[y][x+1]==3 && maze[y+1][x]==1) || (maze[y][x+1]==3 && maze[y+1][x]==3))
                {
                    tmp_state[x-1]=tmp_state[x]=3;
                    hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
                }
            }
        }
    }
}

void dp_edge(int y, int x, int now, int k)//对于端点dp
{
    for(int i=0;i<hm[now].size;++i)
    {
        decode(tmp_state, M, hm[now].key[i]);
        int left=tmp_state[x-1], up=tmp_state[x];
        if((left==k && up==0) || (left==0 && up==k))//起始端点
        {
            tmp_state[x-1]=tmp_state[x]=0;
            if(x==M)
                shift(tmp_state, M);
            hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
        }
        else if(!left && !up)//终止端点
        {
            if(maze[y][x+1]==1 || maze[y][x+1]==k)
            {
                tmp_state[x-1]=0;
                tmp_state[x]=k;
                hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
            }
            if(maze[y+1][x]==1 || maze[y+1][x]==k)
            {
                tmp_state[x-1]=k;
                tmp_state[x]=0;
                if(x==M)
                    shift(tmp_state, M);
                hm[!now].insert(encode(tmp_state, M), hm[now].val[i]+1);
            }
        }
    }
}

void solve()
{
    bool now=0;
    hm[now].clear();
    hm[now].insert(0, 0);
    for(int i=1;i<=N;++i)
        for(int j=1;j<=M;++j)
        {
            hm[!now].clear();
            if(maze[i][j]==0)
                dp0(i, j, now);
            else if(maze[i][j]==1)
                dp1(i, j, now);
            else dp_edge(i, j, now, maze[i][j]);
            now^=1;
        }
    int ans=0;
    for(int i=0;i<hm[now].size;++i)
        ans+=hm[now].val[i];
    if(ans>0)
        ans-=2;
    printf("%d\n", ans);
}

int main()
{
    while(~scanf("%d%d", &N, &M)&&(N||M))
    {
        mem(maze,0);
        for(int i=1;i<=N;++i)
            for(int j=1;j<=M;++j)
            {
                scanf("%d",&maze[i][j]);
                if(maze[i][j]<=1)//01交换
                    maze[i][j]^=1;
            }
        solve();
    }
    
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值