题目描述
话说我们铭铭小朋友成功的回答了爸爸的问题,自然少不了要去索要些奖励,抠门的爸爸一看报纸,嘿,门口的麦当劳在搞活动,还有免费午餐哦,不过前提条件:得正确回答麦当劳叔叔的问题。
问题是这样描述的:
“我面前有很多个小朋友,我希望你帮我找到一个最聪明的小朋友。我心目中最聪明的就是第一个跑进麦当劳大门的,我希望你帮我找出最聪明和最不聪明的小朋友,可能的最大的到达时间差。但是,小朋友只能按照一个特殊的规则前进。小朋友面前有一个 n\times nn×n 的格子矩阵,左下角的格子是起点,右上角的格子是大门。每个孩子每秒可以走向 上、下、左、右 前进一个格子,每个格子只能经过一次。但矩阵中间有一些障碍区,不能通过,只能绕过。”
例如,4\times 44×4 的矩阵,格子 (1, 1),(2, 3),(4, 2)(1,1),(2,3),(4,2) 为障碍区,黑格子就是一条可行的路线。时间为 77。
输入格式
第一行为两个整数 n, mn,m。
第二至第 m+1m+1 行里,每行描述一个障碍区。用两个整数表示 x, yx,y。
输出格式
仅一行,那个最大的时间差。
输入输出样例
输入 #1复制
4 3 1 1 2 3 4 2
输出 #1复制
4
题解
题意简述:给一个长和宽都为 nn 的方阵,方阵上有的点为 00,有的点为 11,为 00 的点不能走,要求出从左下角走到右上角的最长路线和最短路线的差值。
分析
本题解默认所有读者都已经会插头 DP 思想以及其模板,如果不会请先学习再来阅读!模板指路
最短路很好求,无论是 BFS 还是 DFS 都能在正确的时间复杂度内跑出结果,所以我们主要要求最长路。看到 n\le10n≤10 的时候第一反应是搜索,但是看了一眼讨论区,搜索的复杂度是假的,结合这么小的数据,比较能想到的就是状压 DP,然后便能比较自然的想到是插头 DP 了。
思考怎么进行插头 DP,显然,这道题要求的是一条路径,而不是一条回路。那我们可以尝试将问题转换成我们熟悉的范围内:求回路。(不确定求路径能不能做,因为我尝试过写求路径然后成功的写挂了)。
有一个十分巧妙的转换,不过这种转化用的也比较多,就是在原方阵外面再围一圈作为哨兵。(这种思想在很多搜索题里面都存在,来避免一些边界问题,当然,这道题用到的思想不太一样,只是类似而已)。
如图所示(样例):其中 11 代表能走的。
注意,因为题目让我们求左下角到右上角,但是这样我们就要倒着遍历,很麻烦,那我们把整个方阵做一个竖直翻转,变为从左上角走到右下角即可。
我们发现,原图被我们规定在了右上角,而到从有哨兵的图到原图只有两个地方能过去,也就是原图起点的左边和原图终点的下面,这样围成的回路我们能保证一定经过原图的起点和终点,也就是我们的路径中有一段全是哨兵,而另一段就是最长路。求最长路只需减掉哨兵就好。
那么我们发现现在其实就是插头 DP 的板子了,板子指路,不过我们把求方案数改成了求路径长度。也很简单,我们不存方案数存路径长度,转移时取最大的而不是相加即可。
代码
代码不难,不过有有一些小技巧,比如说初始化时把长宽都为 n+2n+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;
}