洛谷P1713 麦当劳叔叔的难题题解

题目:

题面戳我哦

题意简述:给一个长和宽都为 n 的方阵,方阵上有的点为 0,有的点为 1,为 0 的点不能走,要求出从左下角走到右上角的最长路线和最短路线的差值。

分析

本题解默认所有读者都已经会插头 DP 思想以及其模板,如果不会请先学习再来阅读!模板指路

最短路很好求,无论是 BFS 还是 DFS 都能在正确的时间复杂度内跑出结果,所以我们主要要求最长路。看到 n≤10 的时候第一反应是搜索,但是看了一眼讨论区,搜索的复杂度是假的,结合这么小的数据,比较能想到的就是状压 DP,然后便能比较自然的想到是插头 DP 了。

思考怎么进行插头 DP,显然,这道题要求的是一条路径,而不是一条回路。那我们可以尝试将问题转换成我们熟悉的范围内:求回路。(不确定求路径能不能做,因为我尝试过写求路径然后成功的写挂了)。

有一个十分巧妙的转换,不过这种转化用的也比较多,就是在原方阵外面再围一圈作为哨兵。(这种思想在很多搜索题里面都存在,来避免一些边界问题,当然,这道题用到的思想不太一样,只是类似而已)。

如图所示(样例):其中 1 代表能走的。

注意,因为题目让我们求左下角到右上角,但是这样我们就要倒着遍历,很麻烦,那我们把整个方阵做一个竖直翻转,变为从左上角走到右下角即可。

我们发现,原图被我们规定在了右上角,而到从有哨兵的图到原图只有两个地方能过去,也就是原图起点的左边和原图终点的下面,这样围成的回路我们能保证一定经过原图的起点和终点,也就是我们的路径中有一段全是哨兵,而另一段就是最长路。求最长路只需减掉哨兵就好。

那么我们发现现在其实就是插头 DP 的板子了,板子指路,不过我们把求方案数改成了求路径长度。也很简单,我们不存方案数存路径长度,转移时取最大的而不是相加即可。

代码

代码不难,不过有有一些小技巧,比如说初始化时把长宽都为 n+2 的方阵初始化为 11,这样在进行插头 DP 的时候,不是 11 的要么是越界要么是障碍物,省去了判断边界的步骤。

#include <bits/stdc++.h>
using namespace std;

const int N = 11810 , M = 23007;

int n , m;
int g[20][20];
int h[2][M] , q[2][N] , cnt[2];
int v[2][M];
bool vis[20][20];
int dx[] = {0 , 1 , -1 , 0} , dy[] = {1 , 0 , 0 , -1};
struct node 
{
	int x , y , step;
};

inline int find (int cur , int x)//find和insert都是哈希表用的函数
{
	int t = x%M;
	while (h[cur][t]!=-1 && h[cur][t]!=x)
	{
		if (++t==M) t = 0;
	}
	return t;
}

inline void insert (int cur , int state , int w)
{
	int t = find (cur , state);
	if (h[cur][t]==-1)
	{
		h[cur][t] = state , v[cur][t] = w;
		q[cur][++cnt[cur]] = t;
	}
	else v[cur][t] = max(w , v[cur][t]);
}

inline int get (int state , int k)//返回第k位的值
{
	return (state>>(k*2))&3;
}

inline int build (int k , int x)//返回第k位为x在10进制下的值
{
	return x*(1<<(k*2));
}

void print(int state)//调试不用管,不过这个调试很好用,分享一下
{
	int temp=n+1;
	while(temp)
	{
		cout<<(state&3);
		state>>=2;
		--temp;
	}
}

inline int get_max ()//插头DP求最长路
{
	int res = -1e8;
	int cur = 0;
	memset (h , -1 , sizeof h);
	insert (cur , 0 , 1);
	for (int i=1 ; i<=n ; i++)
	{
		for (int j=1 ; j<=cnt[cur] ; j++)//换行
		{
			h[cur][q[cur][j]] <<= 2;
		}
		for (int j=1 ; j<=n ; j++)
		{
//			cout << "i:" << i << " j:" << j << '\n';
			int last = cur;
			cur ^= 1 , cnt[cur] = 0;
			memset (h[cur] , -1 , sizeof h[cur]);
			for (int k=1 ; k<=cnt[last] ; k++)
			{
				int state = h[last][q[last][k]] , w = v[last][q[last][k]];
				int x = get (state , j-1) , y = get (state , j);
//				print(state);
//				cout << ' ' << w << ' ' << x << ' ' << y << '\n';
				if (!g[i][j])
				{ 
					insert (cur , state , w);
				}
				else if (!x && !y)
				{
					insert (cur , state , w);
					if (g[i+1][j] && g[i][j+1]) 
					{
						insert (cur , state+build(j-1 , 1)+build(j , 2) , w+1);
					}
				}
				else if (!x && y)
				{
					if (g[i][j+1]) insert (cur , state , w+1);
					if (g[i+1][j]) insert (cur , state-build(j , y)+build(j-1 , y) , w+1);
				}
				else if (x && !y)
				{
					if (g[i+1][j]) insert (cur , state , w+1);
					if (g[i][j+1]) insert (cur , state-build(j-1 , x)+build(j , x) , w+1);
				}
				else if (x==1 && y==1)
				{
					for (int u=j+1 , s=1 ; ; u++)
					{
						int z = get (state , u);
						if (z==1) s++;
						else if (z==2)
						{
							if (--s==0)
							{
								insert (cur , state-build(j , y)-build(j-1 , x)-build(u , 1) , w+1);
								break;
							}
						}
					}
				}
				else if (x==2 && y==2)
				{
					for (int u=j-2 , s=1 ; ; u--)
						{
						int z = get (state , u);
						if (z==2) s++;
						else if (z==1)
						{
							if (--s==0)
							{
								insert (cur , state-build(j , y)-build(j-1 , x)+build(u , 1) , w+1);
								break;
							}
						}
					}
				}
				else if (x==2 && y==1)
				{
					insert (cur , state-build(j-1 , x)-build(j , y) , w+1);
				}
				else if (i==n && j==n)
				{ 
					res = max(res , w);
				}
			}
		}
	}
	return res-2*n-1;
}

inline int get_min ()//最短路很好写
{
	queue <node> q;
	q.push ({1 , 3 , 1});
	while (!q.empty())
	{
		int x = q.front().x , y = q.front().y , step = q.front().step;
		q.pop();
		if (x==n-2 && y==n) 
		{
//			cout << step << '\n';
			return step;
		}
		vis[x][y] = 1;
		for (int i=0 ; i<4 ; i++)
		{
			int xx = dx[i]+x , yy = dy[i]+y;
			if (xx>=1 && xx<=n-2 && yy>=3 && yy<=n && g[xx][yy] && !vis[xx][yy])
			{
				q.push({xx , yy , step+1});
			}
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL); cout.tie(NULL);
	
	cin >> n >> m;
	n += 2;
	for (int i=1 ; i<=n ; i++)//全部初始化为1,避免某些边界问题
	{
		for (int j=1 ; j<=n ; j++)
		{
			g[i][j] = 1;
		}
	}
	for (int i=2 ; i<=n-1 ; i++)//添加不能走的哨兵
	{
		g[i][2] = 0 , g[2][i] = 0;
	}
	for (int i=1 ; i<=m ; i++)
	{
		int a , b;
		cin >> a >> b;
		g[a+2][b+2] = 0;
	}
	
	for (int i=1 ; i<=n/2 ; i++)//翻转矩阵
	{
		int tmp[20];
		memcpy (tmp , g[i] , sizeof g[i]);
		memcpy (g[i] , g[n-i+1] , sizeof g[i]);
		memcpy (g[n-i+1] , tmp , sizeof tmp);
	}
	
//	for (int i=1 ; i<=n ; i++)
//	{
//		for (int j=1 ; j<=n ; j++)
//		{
//			cout << g[i][j] << ' ';
//		}
//		cout << '\n';
//	}
	
	int res_max = get_max () , res_min = get_min ();
	cout << res_max-res_min << '\n';
	return 0;
}
//勿抄

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值