poj3133之经典插头DP

Manhattan Wiring

Time Limit: 5000MS   Memory Limit: 65536K
Total Submissions: 1482   Accepted: 869

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.

n m
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

题意:求连接两个2和两个3的路径之和最小,输出和-2,不存在就输出0

输入0表示可以走,1表示不可以走

分析:按照插头DP的模式进行DP,碰到0时如果p=q=0可以选择不走

碰到2和3的时候保证只有一个插头即可

这题有很多细节要注意

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <map>
#include <cmath>
#include <iomanip>
#define INF 99999999
typedef long long LL;
using namespace std;

const int MAX=30000+10;
const int maxn=100000+10;
const int N=10+10;
int n,m,size[2],index,bit[N];
int head[MAX],next[maxn];
LL dp[2][maxn],state[2][maxn],sum;
int mp[N][N];

void HashCalState(LL s,LL num){
	int pos=s%MAX;
	for(int i=head[pos];i != -1;i=next[i]){
		if(state[index][i] == s){
			dp[index][i]=min(dp[index][i],num);
			return;
		}
	}
	state[index][size[index]]=s;
	dp[index][size[index]]=num;
	next[size[index]]=head[pos];
	head[pos]=size[index]++;
}

void DP(){
	//初始化
	sum=INF;
	index=0;
	size[index]=1;
	state[index][0]=0;
	dp[index][0]=0;
	//逐格进行DP
	for(int i=1;i<=n;++i){
		for(int k=0;k<size[index];++k)state[index][k]<<=2;//上一行最后的空插头减去,本行最前面增加一个空插头,所以*4
		for(int j=1;j<=m;++j){
			memset(head,-1,sizeof head);
			index=index^1;
			size[index]=0;
			for(int k=0;k<size[index^1];++k){
				LL s=state[index^1][k],temp;
				LL num=dp[index^1][k];
				int p=(s>>bit[j-1])%4;
				int q=(s>>bit[j])%4;
				int w=(s>>bit[j+1])%4;
				if(mp[i][j] == 1){//该格不能走
					if(!p && !q)HashCalState(s,num);
					continue;
				}else if(!p && !q){
					if(!mp[i][j]){//该点是0需要两个插头或者不走
					 	HashCalState(s,num);//该点是0可以不走
						if(mp[i][j+1] == 1 || mp[i+1][j] == 1)continue;//右边或下边相邻的格子不能到达,无法完成两个插头
						if(mp[i][j+1]+mp[i+1][j] == 5)continue;//表示添加的两个插头两端将是2和3,不能到达
						if(mp[i][j+1] == 2 || mp[i+1][j] == 2){//有点是2则必须连得插头是1(表示连2的线)
							temp=s+(1<<bit[j-1])+(1<<bit[j]);
							if(w == 0)HashCalState(temp,num+3);//mp[i][j+1]没有插头则经过该点路径长度+1,即num+2+1
							else if(w == 1 && mp[i][j+1] != 2)HashCalState(temp,num+2);//增加的路径只有[i+1,j]和[i,j]
						}
						else if(mp[i][j+1] == 3 || mp[i+1][j] == 3){//有点是3则必须连得插头是2(表示连3的线)
							temp=s+2*(1<<bit[j-1])+2*(1<<bit[j]);
							if(w == 0)HashCalState(temp,num+3);
							else if(w == 2 && mp[i][j+1] != 3)HashCalState(temp,num+2);//特别要注意w != 0时要判断[i,j+1]是否是2或3,防止2/3的情况下有两个插头
						}
						else {
							if(w == 0){
								HashCalState(s+(1<<bit[j-1])+(1<<bit[j]),num+3);
								HashCalState(s+2*(1<<bit[j-1])+2*(1<<bit[j]),num+3);
							}else if(w == 1 && mp[i][j+1] != 2)HashCalState(s+(1<<bit[j-1])+(1<<bit[j]),num+2);
							else if(w == 2 && mp[i][j+1] != 3)HashCalState(s+2*(1<<bit[j-1])+2*(1<<bit[j]),num+2);
						}
					}
					else if(mp[i][j] == 2){//只能有独立插头
						if(mp[i][j+1] != 1 && mp[i][j+1] != 3){
							if(w == 0)HashCalState(s+(1<<bit[j]),num+2);
							else if(w == 1 && mp[i][j+1] != 2)HashCalState(s+(1<<bit[j]),num+1);
						}
						if(mp[i+1][j] != 1 && mp[i+1][j] != 3)HashCalState(s+(1<<bit[j-1]),num+2);
					}
					else if(mp[i][j] == 3){//只能有独立插头
						if(mp[i][j+1] != 1 && mp[i][j+1] != 2){
							if(w == 0)HashCalState(s+2*(1<<bit[j]),num+2);
							else if(w == 2 && mp[i][j+1] != 3)HashCalState(s+2*(1<<bit[j]),num+1);
						}
						if(mp[i+1][j] != 1 && mp[i+1][j] != 2)HashCalState(s+2*(1<<bit[j-1]),num+2);
					}
				}else if(!p && q){
					if(mp[i][j]){
						//if(mp[i][j] != q+1)continue;
						s=s-q*(1<<bit[j]);
						HashCalState(s,num);
					}else{
						if(mp[i][j+1] == 0 || mp[i][j+1] == q+1){
							if(w == 0)HashCalState(s,num+1);
							else if(w == q && mp[i][j+1] == 0)HashCalState(s,num);
						}
						if(mp[i+1][j] == 0 || mp[i+1][j] == q+1){
							s=s+q*(1<<bit[j-1])-q*(1<<bit[j]);
							HashCalState(s,num+1);
						}
					}
				}else if(p && !q){
					if(mp[i][j]){
						//if(mp[i][j] != p+1)continue;
						s=s-p*(1<<bit[j-1]);
						HashCalState(s,num);
					}else{
						if(mp[i+1][j] == 0 || mp[i+1][j] == p+1)HashCalState(s,num+1);
						if(mp[i][j+1] == 0 || mp[i][j+1] == p+1){
							s=s-p*(1<<bit[j-1])+p*(1<<bit[j]);
							if(w == 0)HashCalState(s,num+1);
							else if(w == p && mp[i][j+1] == 0)HashCalState(s,num);
						}
					}
				}else if(p == q/*&& !mp[i][j]*/){//p == q == 1或者p == q == 2时p‘,q‘不能有插头
					s=s-p*(1<<bit[j-1])-q*(1<<bit[j]);
					HashCalState(s,num);
				}
			}
		}
	}
	//cout<<size[index]<<endl;
	for(int k=0;k<size[index];++k)sum=min(sum,dp[index][k]);
}

int main(){
	for(int i=0;i<N;++i)bit[i]=i<<1;
	while(~scanf("%d%d",&n,&m),n+m){
		for(int i=0;i<N;++i)for(int j=0;j<N;++j)mp[i][j]=1;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j)cin>>mp[i][j];
		}
		DP();//插头DP
		if(sum == INF)sum=2;
		printf("%lld\n",sum-2);
	}
	return 0;
}
/*
5 9
0 0 0 0 0 0 0 0
0 0 0 3 0 0 0 0
2 0 0 0 0 0 2 0
0 0 0 3 0 0 0 0
0 0 0 0 0 0 0 0

5 6
0 0 0 0 0 0
0 0 0 3 0 0
0 2 0 0 0 2
0 0 0 3 0 0
0 0 0 0 0 0
*/
 

poj3133之经典插头DP,码迷,mamicode.com

时间: 2024-10-10 12:18:26

poj3133之经典插头DP的相关文章

初探插头dp

开学那个月学了点新东西,不知道还记不记得了,mark一下 感觉cdq的论文讲的很详细 题主要跟着kuangbin巨做了几道基础的 http://www.cnblogs.com/kuangbin/archive/2012/10/02/2710343.html 还有几道没做,留着坑 感觉广义括号表示法虽然神奇,但一般最小表示法就够用了吧,感觉最小表示法更直观一点 hdu1693 1 #include<cstdio> 2 #include<iostream> 3 #include<

插头dp

对于网格中的dp可以用轮廓线,如果有一些格子不能走就可以用插头dp了. bzoj2331 地板 题目大意:用L型铺地n*m,有一些格子不能铺,求方案数. 思路:fi[i][j][s]表示铺到(i,j),轮廓线状态s,0表示没有插头,1表示插头没拐弯,2表示插头拐弯了,手动枚举转移. 注意:(1)按四进制好写: (2)因为实际状态和四进制的差很多,所以用hash表来存储,防止mle和tle,同时使用滚动数组. #include<iostream> #include<cstdio> #

[入门向选讲] 插头DP:从零概念到入门 (例题:HDU1693 COGS1283 BZOJ2310 BZOJ2331)

转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.html 最近搞了一下插头DP的基础知识……这真的是一种很锻炼人的题型…… 每一道题的状态都不一样,并且有不少的分类讨论,让插头DP十分锻炼思维的全面性和严谨性. 下面我们一起来学习插头DP的内容吧! 插头DP主要用来处理一系列基于连通性状态压缩的动态规划问题,处理的具体问题有很多种,并且一般数据规模较小. 由于棋盘有很特殊的结构,使得它可以与“连通性”有很强的联系,因此插头DP最常见的应用要数

poj 1947 经典树形dp

经典树形dp:问在一棵树上最少删除多少条边可以分离出一个节点数为p的子树. 定义状态: dp[i][j]表示从i为根的子树上分离出一个节点数为j的子树的代价(最少需要删除的边数). 考虑i节点的每个儿子ii,ii可以选或者不选(切断),然后就转化成了背包问题. dp[u][j] = min( dp[u][j], dp[u][j - k] + dp[v][k] ); 1 #include <iostream> 2 #include <cstring> 3 #include <c

插头DP学习

队内没人会插头DP,感觉这个不会不行...所以我还是默默去学了一下, 学了一天,感觉会了一点.对于每一行,一共有j+1个插头,如果是多回路类的题目, 比较简单,可以用1表示有插头,0表示没有插头,这样就可以愉快转移了, 对于当前出来的位置(i,j),与它有关的插头有j-1和j 那么我们可以枚举状态经行转移. 对于单回路的问题....只是了解思想,目前还不会写,太笨了=_= poj 2411 Mondriaan's Dream 题意:用1*2小方块组成n*m矩阵有多少种组成方式 思路:我们从上到下

BZOJ 2595 游览计划(插头DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2595 题意:给出一个数字矩阵.求一个连通块包含所有的数字0且连通块内所有数字之和最小. 思路:对于每个格子,是0则必须要选.那么对于不选的格子(i,j)在什么时候可以不选呢?必须同时满足以下两个条件: (1)(i,j)不是0: (2)(i-1,j)不选或者(i-1,j)选了但是轮廓线上还有别的地方与(i-1,j)是一个连通块. int Pre[105][N],op[105][N]; s

ZOJ3329之经典概率DP

One Person Game Time Limit: 1 Second      Memory Limit: 32768 KB      Special Judge There is a very simple and interesting one-person game. You have 3 dice, namely Die1, Die2 and Die3. Die1 has K1 faces. Die2 has K2 faces. Die3 has K3 faces. All the

【插头dp】CDOJ1690 这是一道比CCCC简单题难的简单题

最裸的插头dp,可参见大白书. #include<cstdio> #include<cstring> using namespace std; #define MOD 1000000007 int f[2][(1<<5)+10],n,m; int main(){ scanf("%d%d",&n,&m); int cur=0; f[0][(1<<m)-1]=1; for(int i=0;i<n;++i){ for(in

POJ 2411 Mondriaan&#39;s Dream ——状压DP 插头DP

[题目分析] 用1*2的牌铺满n*m的格子. 刚开始用到动规想写一个n*m*2^m,写了半天才知道会有重复的情况. So Sad. 然后想到数据范围这么小,爆搜好了.于是把每一种状态对应的转移都搜了出来. 加了点优(gou)化(pi),然后poj上1244ms垫底. 大概的方法就是考虑每一层横着放的情况,剩下的必须竖起来的情况到下一层取反即可. 然后看了 <插头DP-从入门到跳楼> 这篇博客,怒抄插头DP 然后16ms了,自己慢慢YY了一下,写出了风(gou)流(pi)倜(bu)傥(tong)