题意:给一个n*m的矩阵,每个格子中有1个数,可能是0或2或3,出现2的格子数为2个,出现3的格子数为2个,要求将两个2相连,两个3相连,求不交叉的最短路(起终点只算0.5长,其他算1)。

思路:

  这题与普通插头DP有些区别了,就是要求最短路时,而且还要同时两条不相交的最短路。一开始看还是感觉挺难的,因为每个0格子还得考虑两种线,分别是2线和3线,而且还不能出现回路的,于是就想用之前的模板,括号表示法,再加上1个位来表示2/3线,即共3个位来表示一个插头信息,但是写了才发现,括号表示不了,比如仅让右括号进2/3的格子时,如果2/3的格子在最左边时就不行了,同理左括号也不行,并不是真不行,是有点难搞。

  于是盯着刘汝佳的书看,仅需3进制就能实现,这么神奇?只需要标记是2/3线和无线,这3种而已,这里有个问题是“如果产生回路了,怎么办”?由于我们求得是最短路,如果有一个状态是出现回路的,那么这个回路肯定是不必要的,由于在g[i][j]==0时,还可以跳过这些格子,所以肯定也有一个状态也是一样的,只是没有了回路,而且路程更短。总之,如果出现回路,在取最小值时,会自动被代替掉了,因为同样的状态总是会有更短的。

 //#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#define pii pair<int,int>
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
const int N=;
LL ans;
char g[N][N];
int cur, n, m, ex, ey;
struct Hash_Map
{
static const int mod=;
static const int NN=;
int head[mod]; //桶指针
int next[NN]; //记录链的信息
LL status[NN]; //状态
LL value[NN]; //状态对应的DP值。
int size; void clear() //清除哈希表中的状态
{
memset(head, -, sizeof(head));
size = ;
} void insert(LL st, LL val) //插入状态st的值为val
{
int h = st%mod;
for(int i=head[h]; i!=-; i=next[i])
if(status[i] == st) //这个状态已经存在,累加进去。
{
value[i] =min(value[i], val);
return ;
}
status[size]= st; //找不到状态st,则插入st。
value[size] = val;
next[size] = head[h] ; //新插入的元素在队头
head[h] = size++;
}
}hashmap[]; inline int getbit(LL s,int pos) //取出状态s的第pos个插头
{
return (s>>*pos)&;
}
inline int setbit(LL s,int pos,int bit) //将状态s的第pos个插头设置为bit
{
if(s&(<<*pos )) s^=<<(*pos);
if(s&(<<(*pos+))) s^=<<(*pos+);
return (s|(bit<<*pos));
} void DP(int i,int j)
{
for(int k=; k<hashmap[cur^].size; k++)
{
LL s=hashmap[cur^].status[k];
LL v=hashmap[cur^].value[k];
LL t=(setbit(s,j,)&setbit(s,j+,));
int R=getbit(s,j), D=getbit(s,j+); if(g[i][j]==) //障碍格子
{
if( R== && D== ) hashmap[cur].insert(s, v);
continue ;
}
if(R&&D) //相同就能合并,具体看思路
{
if( g[i][j]== && R==D ) hashmap[cur].insert(t,v+);
}
else if(R||D) //单括号:要么需要延续,要么是到达2或3
{
int big=R+D;
if(g[i][j]== && big== ) hashmap[cur].insert(t, v+); //到达
if(g[i][j]== && big== ) hashmap[cur].insert(t, v+);
if(g[i][j]== && i+<n ) hashmap[cur].insert(setbit(t,j,big), v+); //延续
if(g[i][j]== && j+<m ) hashmap[cur].insert(setbit(t,j+,big),v+);
}
else //无括号
{
if(g[i][j]==)
{
if(i+<n) hashmap[cur].insert( setbit(s,j,), v+ );
if(j+<m) hashmap[cur].insert( setbit(s,j+,), v+ );
}
if(g[i][j]==)
{
if(i+<n) hashmap[cur].insert( setbit(s,j,), v+ );
if(j+<m) hashmap[cur].insert( setbit(s,j+,), v+ );
}
if(g[i][j]==)
{
hashmap[cur].insert(s,v); //跳过此格子
if(i+<n && j+<m) hashmap[cur].insert(setbit(t,j,)|setbit(t,j+,), v+ );
if(i+<n && j+<m) hashmap[cur].insert(setbit(t,j,)|setbit(t,j+,), v+ ); }
} } } void cal()
{
for(int i=; i<n; i++)
{
cur^=;
hashmap[cur].clear();
for(int j=; j<hashmap[cur^].size; j++) //新行,需要左移一下状态。
hashmap[cur].insert( hashmap[cur^].status[j]<<, hashmap[cur^].value[j] );
for(int j=; j<m; j++)
{
cur^=;
hashmap[cur].clear();
DP(i,j);
}
}
} LL print()
{
for(int i=; i<hashmap[cur].size; i++)
if(hashmap[cur].status[i]==)
return hashmap[cur].value[i];
return ;
} int main()
{
//freopen("input.txt", "r", stdin);
while(scanf("%d%d",&n,&m), n+m)
{
for(int i=; i<n; i++)
for(int j=; j<m; j++)
scanf("%d",&g[i][j]); cur=;
hashmap[cur].clear();
hashmap[cur].insert(, );
cal();
ans=print();
cout<<(ans==?:ans-)<<endl;
}
return ;
}

AC代码

  自己的想法是这样的:用括号表示法来表示插头,由于有两类线,所以增加一个位来区分2/3线,共3个位表示一个插头信息,所以用1和2表示2线的括号,5和6表示3线的括号,0表示无括号。当遇到2/3格子时,要么只接受单个插头进来,要么只有单方向出去,而且出去的可以是左/右括号,这样才能保证连起来;遇到0空格时,可以不走,也可以产生一对括号,分别是(1,2)和(5,6)这两种。其他的基本一样,实践证明还是可以得,只不过时间复杂度稍微就高了,因为多了很多无用的状态。而且有个注意点是,当同向括号'(('出现时,仍然要合并,而且要改另一个右括号为(,但是这'(('中可能有1个是来自2/3格子的,它仅是单向的线,所以有可能是找不到另一半的,如果找不到另一半就忽略,找到了就改(这说明不是来自2/3格子)。

  注:本代码在POJ上是TLE,但是在UVA或UVALive都是可以过的。

 //#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#define pii pair<int,int>
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
const int N=;
int n, m, cur, g[N][N];
struct Hash_Map
{
static const int mod=;
static const int NN=;
int head[mod]; //桶指针
int next[NN]; //记录链的信息
LL status[NN]; //状态
LL value[NN]; //状态对应的DP值。
int size; void clear() //清除哈希表中的状态
{
memset(head, -, sizeof(head));
size = ;
} void insert(LL st, LL val) //插入状态st的值为val
{
int h = st%mod;
for(int i=head[h]; i!=-; i=next[i])
{
if(status[i] == st) //这个状态已经存在,累加进去。
{
value[i] = min(value[i], val);
return ;
}
} status[size]= st; //找不到状态st,则插入st。
value[size] = val;
next[size] = head[h] ; //新插入的元素在队头
head[h] = size++;
}
}hashmap[]; LL setbit(LL s,int pos,int bit) //将状态s的第pos个插头设置为bit
{
//if(s&(1<<3*pos )) s^=(LL)1<<(3*pos);
//if(s&(1<<(3*pos+1))) s^=(LL)1<<(3*pos+1);
//if(s&(1<<(3*pos+2))) s^=(LL)1<<(3*pos+2);
s&=~(<<*pos);
return (s|((LL)bit<<*pos));
} int getbit(LL s,int pos)
{
return ((s>>*pos)&);
} LL Fr(LL s,int pos,int bit,int rep) //寻找状态s的第pos个插头对应的右括号。
{
int cnt=;
for(pos+=; pos<m; pos++)
{
if(getbit(s, pos)==rep) cnt++;
if(getbit(s, pos)==bit) cnt--;
if(cnt==-) return setbit(s, pos, rep);
}
return s;
}
LL Fl(LL s,int pos,int bit,int rep) //寻找状态s的第pos个插头对应的左括号。
{
int cnt=;
for(pos--; pos>=; pos--)
{
if(getbit(s, pos)==rep) cnt++;
if(getbit(s, pos)==bit) cnt--;
if( cnt==-) return setbit(s, pos, rep);
}
return s; //注意点,有可能找不到对应的另一半括号,Fr同理。
}
void DP(int i,int j)
{
for(int k=; k<hashmap[cur^].size; k++)
{
LL s=hashmap[cur^].status[k];
LL v=hashmap[cur^].value[k];
LL t=(setbit(s,j,)&setbit(s,j+,));
int R=getbit(s, j), D=getbit(s, j+);
if( g[i][j]== ) //障碍
{
if(R== && D==) hashmap[cur].insert(s,v);
continue;
}
if(R&&D)
{
if( g[i][j]== || g[i][j]== || (R&)!=(D&) ) continue; //只能容许1条线进来
if( R==D )
{
if(R==) t=Fr(t,j,,); //左
if(R==) t=Fl(t,j,,); //右
if(R==) t=Fr(t,j,,);
if(R==) t=Fl(t,j,,);
hashmap[cur].insert(t, v-); //当前块计算了2次
}
else if( R== && D== ) hashmap[cur].insert(t,v-);
else if( R== && D== ) hashmap[cur].insert(t,v-);
}
else if(R||D) //单括号
{
R=max(R,D);
if( g[i][j]== && (R==||R==) ) hashmap[cur].insert(t, v);
if( g[i][j]== && (R==||R==) ) hashmap[cur].insert(t, v);
if( g[i][j]== && j+<m ) hashmap[cur].insert(setbit(t,j+,R), v+); //延续
if( g[i][j]== && i+<n ) hashmap[cur].insert(setbit(t,j,R), v+);
}
else //无括号
{
if(g[i][j]==) //起终点格子:只可以单线出。
{
if(i+<n) hashmap[cur].insert(setbit(t,j,) , v+); //下
if(i+<n) hashmap[cur].insert(setbit(t,j,) , v+); //下
if(j+<m) hashmap[cur].insert(setbit(t,j+,), v+); //右
if(j+<m) hashmap[cur].insert(setbit(t,j+,), v+); //右
}
if(g[i][j]==)
{
if(i+<n) hashmap[cur].insert(setbit(t,j,) , v+); //下
if(i+<n) hashmap[cur].insert(setbit(t,j,) , v+); //下
if(j+<m) hashmap[cur].insert(setbit(t,j+,), v+); //右
if(j+<m) hashmap[cur].insert(setbit(t,j+,), v+); //右
}
if(g[i][j]==)
{
hashmap[cur].insert(s, v); //本格子不走
if(i+<n && j+<m)
{
hashmap[cur].insert(setbit(t,j,)|setbit(t,j+,), v+); //2线角头
hashmap[cur].insert(setbit(t,j,)|setbit(t,j+,), v+); //3线角头
}
}
}
}
} void cal()
{
for(int i=; i<n; i++)
{
cur^=;
hashmap[cur].clear();
for(int j=; j<hashmap[cur^].size; j++) //新行,需要左移一下状态。
hashmap[cur].insert( hashmap[cur^].status[j]<<, hashmap[cur^].value[j] );
for(int j=; j<m; j++)
{
cur^=;
hashmap[cur].clear();
DP(i,j);
}
}
}
LL print()
{
for(int i=; i<hashmap[cur].size; i++)
if(hashmap[cur].status[i]==)
return hashmap[cur].value[i];
return ;
} int main()
{
freopen("input.txt", "r", stdin);
while(scanf("%d%d",&n,&m), n+m)
{
for(int i=; i<n; i++)
for(int j=; j<m; j++)
scanf("%d",&g[i][j]); cur=;
hashmap[cur].clear();
hashmap[cur].insert(, );
cal();
LL ans=print();
printf("%lld\n",ans==?:ans-);
}
return ;
}

AC代码

POJ 3133 Manhattan Wiring (插头DP,轮廓线,经典)的更多相关文章

  1. poj 3133 Manhattan Wiring

    http://poj.org/problem?id=3133 考虑插头 dp 用四进制表示一个插头的状态,0 表示没有插头,2 表示这个插头是连接两个 2 的,3 同理 然后就是大力分类讨论了 这题还 ...

  2. uva1214 Manhattan Wiring 插头DP

    There is a rectangular area containing n × m cells. Two cells are marked with “2”, and another two w ...

  3. 【POJ】3133 Manhattan Wiring

    http://poj.org/problem?id=3133 题意:n×m的网格,有2个2,2个3,他们不会重合.还有障碍1.现在求2到2的路径和3到3的路径互不相交的最短长度-2.(2<=n, ...

  4. POJ - 1185 炮兵阵地 (插头dp)

    题目链接 明明是道状压dp的题我为啥非要用插头dp乱搞啊 逐行枚举,设dp[i][S]为枚举到第i个格子时,状态为S的情况.S为当前行上的“插头”状态,每两个二进制位表示一个格子,设当前格子为(x,y ...

  5. poj 2096 Collecting Bugs 概率dp 入门经典 难度:1

    Collecting Bugs Time Limit: 10000MS   Memory Limit: 64000K Total Submissions: 2745   Accepted: 1345 ...

  6. POJ 2152 Fire (树形DP,经典)

    题意:给定一棵n个节点的树,要在某些点上建设消防站,使得所有点都能够通过某个消防站解决消防问题,但是每个点的建站费用不同,能够保证该点安全的消防站的距离上限也不同.给定每个点的建站费用以及最远的消防站 ...

  7. 插头DP专题

    建议入门的人先看cd琦的<基于连通性状态压缩的动态规划问题>.事半功倍. 插头DP其实是比较久以前听说的一个东西,当初是水了几道水题,最近打算温习一下,顺便看下能否入门之类. 插头DP建议 ...

  8. 【模板】插头dp

    题目描述 题解: 插头$dp$中经典的回路问题. 首先了解一下插头. 一个格子,上下左右四条边对应四个插头.就像这样: 四个插头. 一个完整的哈密顿回路,经过的格子一定用且仅用了两个插头. 所以所有被 ...

  9. [Poj3133]Manhattan Wiring (插头DP)

    Description 题目大意:给你个N x M(1≤N, M≤9)的矩阵,0表示空地,1表示墙壁,2和3表示两对关键点.现在要求在两对关键点之间建立两条路径,其中两条路径不可相交或者自交(就是重复 ...

随机推荐

  1. Spring Boot2中配置HTTPS

    1.生成证书 使用jdk,jre中的keytool.exe生成自签名的证书,需要配置JAVA_HOME和path环境变量,即jdk的环境变量.命令如下: keytool -genkey -alias ...

  2. [Xcode 实际操作]九、实用进阶-(27)字符串文件(Localizable.strings)的本地化

    目录:[Swift]Xcode实际操作 本文将演示字符串文件(Localizable.strings)的本地化. 在项目[DemoApp]文件夹下点击鼠标右键,弹出右键菜单 ->[New Fil ...

  3. web前端篇:html基础知识

    目录 1.web前端: 2.HTML概述 2.1HTML介绍 2.2HTML在计算机中如何表现 3.HTML基础语法 4.练习题: 1.web前端: 什么是web? web 就是网页,是一种基于B/S ...

  4. Eclipse开发MR环境搭建

    1.jdk环境配置     jdk安装后好后配置相关JAVA_HOME环境变量,并将bin目录配置到path 2. 下载hadoop-2.7.1.tar.gz 解压hadoop-2.7.1.tar.g ...

  5. Linux 根据进程ID查看文件路径(转)

    遇到的问题是想要查看进程的启动脚本在哪里,比如自己写的weblogic启动脚本,但忘记放在哪里了,这时候可以用以下方式 1.用ps -ef |grep xxxxx 得到该进程的pid 2.输入ls - ...

  6. dubbo-admin安装

    1.下载dubbo-adminhttps://github.com/apache/incubator-dubbo/tree/dubbo-2.5.7 2.解压,进入到/home/zhanxuewei/D ...

  7. android 多线程下载思路

    首先请求下载url,获取文件大小和文件类型 比如获取到文件大小是7410642  文件类型为application/vnd.android.package-archive(即后缀为apk,安卓app安 ...

  8. collections 中 typing 中对象的引用

    from typing import ( Callable as Callable, Container as Container, Hashable as Hashable, Iterable as ...

  9. 牛客寒假6-B.煤气灶

    链接:https://ac.nowcoder.com/acm/contest/332/B 题意: 小j开始打工,准备赚钱买煤气灶. 第一天,小j的工资为n元,之后每天他的工资都比前一天多d元. 已知煤 ...

  10. Java EE学习笔记(七)

    MyBatis的核心配置 1.MyBatis的核心对象 1).SqlSessionFactory是MyBatis框架中十分重要的对象,它是单个数据库映射关系经过编译后的内存镜像,其主要作用是创建Sql ...