P1713 麦当劳叔叔的难题

题目描述

话说我们铭铭小朋友成功的回答了爸爸的问题,自然少不了要去索要些奖励,抠门的爸爸一看报纸,嘿,门口的麦当劳在搞活动,还有免费午餐哦,不过前提条件:得正确回答麦当劳叔叔的问题。

问题是这样描述的:

“我面前有很多个小朋友,我希望你帮我找到一个最聪明的小朋友。我心目中最聪明的就是第一个跑进麦当劳大门的,我希望你帮我找出最聪明和最不聪明的小朋友,可能的最大的到达时间差。但是,小朋友只能按照一个特殊的规则前进。小朋友面前有一个 n×n的格子矩阵,左下角的格子是起点,右上角的格子是大门。每个孩子每秒可以走向 上、下、左、右 前进一个格子,每个格子只能经过一次。但矩阵中间有一些障碍区,不能通过,只能绕过。”

例如,4×4的矩阵,格子 (1,1),(2,3),(4,2)为障碍区,黑格子就是一条可行的路线。时间为 77。

输入格式

第一行为两个整数 n,m。

第二至第 m+1 行里,每行描述一个障碍区。用两个整数表示 x,y。

输出格式

仅一行,那个最大的时间差。

输入输出样例

输入 #1复制

4 3
1 1
2 3
4 2

输出 #1复制

4

说明/提示

2≤n≤10,0≤m≤100,1≤x,y≤n。

第一次写解析

题意

有一个 n×nn×n 的矩阵,其中有 mm 个障碍格,问从左下角到右上角的最长路与最短路的长度差。

其中 2≤n≤10,0≤m≤100,1≤x,y≤n。

比如样例的图(最短长度为 6):

最长(长度为 10):

思路

最短路好求,直接 bfs(其实是懒得再写一个插头 dp)。

那最长路呢?只能用插头 dp 了。

想一下三种表示方式的区别:

  1. 括号表示法:适用于求一条回路。
  2. 01 表示法:求骨牌覆盖。
  3. 最小表示法:求一块连通块。

显然,这道题只能用括号表示法。

但是括号表示法只能求回路,怎么求路径呢?

这个问题本蒟蒻想了一天,终于想到了。我们可以把原图围起来,在另辟一条路出来,与原路径形成回路(右下角 4×4 的是原图):

bfs 出来的最短路径只要加上 2×(n−1)就行了。

这时候最长路形成的回路也一定是走了外面这条路的,毕竟起点门口就有一个障碍,不走不行啊。

最后我们 dp 最长回路就行了。

Code:

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N=13;//加了2层
const int mod=590037;//hash表模数
int n, m, ans=0;
bool mp[N][N];//mp[][]:1空地0障碍
//括号表示法4进制
int head[mod], nxt[mod], id[mod], toth;//hash表使用链式存储
int dp[2][mod], state[2][mod], tot[2];//空间不够滚起来
int from=1, to=0;//滚动dp
int base[N];为了方便提取插头(用2进制存4进制)
bool vis[N][N];//bfs用
int dis[N][N];//bfs用存最短路
int dx[]={0, -1, 0, 1};//方向数组
int dy[]={-1, 0, 1, 0};
queue<pair<int, int> > q;
void bfs(int x, int y){//bfs求最短路
	vis[x][y]=1;dis[1][1]=1;
	q.push(make_pair(x, y));
	while(q.size()){
		pair<int, int> now=q.front();q.pop();
		int xx=now.first, yy=now.second;
		for(int i=0;i<4;i++){
			int xxx=xx+dx[i], yyy=yy+dy[i];
			if(mp[xxx][yyy] && !vis[xxx][yyy]){
				dis[xxx][yyy]=dis[xx][yy]+1;
				vis[xxx][yyy]=1;
				q.push(make_pair(xxx, yyy));
			}
		}
	}
}
void add(int sta, int val){//放进hash表
	int key=sta%mod;
	for(int i=head[key];i;i=nxt[i]){
		if(state[to][id[i]]==sta){//找到是不是已经有了这个状态
			dp[to][id[i]]=max(dp[to][id[i]], val);
			return;
		}
	}
	tot[to]++;//没有这个状态,新加入一个
	state[to][tot[to]]=sta;
	dp[to][tot[to]]=val;
	toth++;
	nxt[toth]=head[key];
	head[key]=toth;
	id[toth]=tot[to];
}
int main(){
	cin>>n>>m;
	n+=2;
	while(m--){
		int x, y;
		cin>>x>>y;
		mp[n-x+1][y]=1;//我是从(1,1)dp到(n,n),所以倒过来
	}
	for(int j=2;j<n;j++)//放障碍围住原图
		mp[2][j]=1;
	for(int i=2;i<n;i++)//放障碍围住原图
		mp[i][n-1]=1;
	for(int j=2;j<=n;j++)//bfs不让走外面
		vis[1][j]=1;
	for(int i=2;i<n;i++)//bfs不让走外面
		vis[i][n]=1;
	for(int i=1;i<=n;i++)//取反(这样边界也就围起来了)
		for(int j=1;j<=n;j++)
			mp[i][j]^=1;
	for(int i=1;i<=n;i++)//预处理base
		base[i]=(i<<1);
	tot[to]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(1<<(n+1));j++)//行之间转移,扔掉最右边的一个没用的线
			state[to][j]<<=2;
		for(int j=1;j<=n;j++){
			swap(from, to);
			toth=tot[to]=0;
			memset(head, 0, sizeof(head));//滚动数组
			for(int k=1;k<=tot[from];k++){
				int nowsta=state[from][k], nowans=dp[from][k];
				int down=(nowsta>>base[j])&3, right=(nowsta>>base[j-1])&3;
				if(!mp[i][j]){//#1:有障碍
					if(!down && !right)
						add(nowsta, nowans);
				}else if(!down && !right){//#2:没插头
					if(i!=1 || j!=1)
						add(nowsta, nowans);
					if(mp[i+1][j] && mp[i][j+1])
						add(nowsta+(1<<base[j-1])+(2<<base[j]), nowans+1);
				}else if(!down && right){//#3:只有左边插头
					if(mp[i+1][j])
						add(nowsta, nowans+1);//down
					if(mp[i][j+1])
						add(nowsta-(right<<base[j-1])+(right<<base[j]), nowans+1);//right
				}else if(down && !right){//#4:只有右边插头
					if(mp[i][j+1])
						add(nowsta, nowans+1);//right
					if(mp[i+1][j])
						add(nowsta-(down<<base[j])+(down<<base[j-1]), nowans+1);//down
				}else if(down==1 && right==1){//合并两个插头
					int cnt=1;
					for(int t=j+1;t<=n;t++){
						if(((nowsta>>base[t])&3)==1)
							cnt++;
						if(((nowsta>>base[t])&3)==2)
							cnt--;
						if(cnt==0){
							add(nowsta-(1<<base[t])-(1<<base[j-1])-(1<<base[j]), nowans+1);
							break;
						}
					}
				}else if(down==2 && right==2){//合并
					int cnt=1;
					for(int t=j-2;t>=0;t--){
						if(((nowsta>>base[t])&3)==2)
							cnt++;
						if(((nowsta>>base[t])&3)==1)
							cnt--;
						if(cnt==0){
							add(nowsta+(1<<base[t])-(2<<base[j-1])-(2<<base[j]), nowans+1);
							break;
						}
					}
				}else if(down==1 && right==2){//合并
					add(nowsta-(right<<base[j-1])-(down<<base[j]), nowans+1);
				}else if(down==2 && right==1){//形成回路
					if(i==n && j==n && (nowsta-(down<<base[j])-(right<<base[j-1]))==0)
						ans=max(ans, nowans+1);
				}
			}
		}
	}
	bfs(1, 1);
	cout<<ans-(dis[n][n]-1+2*(n-1))<<endl;//作差,输出
	return 0;
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值