3828. 行走路径
题意:
给定一个 n*m 的矩阵,每个位置有一个数 x (1≤x≤4)。
规定只能按照
1
→
2
→
3
→
4
→
1
→
2
→
.
.
.
1→2→3→4→1→2→...
1→2→3→4→1→2→... 的顺序行走,并且称
1
→
2
→
3
→
4
1→2→3→4
1→2→3→4 为一轮。
问,在这个矩阵中,最多能走多少轮?如果出现能一直走下去,输出 -1。
1 ≤ n , m ≤ 1000 1≤n,m≤1000 1≤n,m≤1000
做法1:拓扑序+dp
反向建边,按照拓扑序来更新。
如果走到的点为1,就让轮数+1。
需要注意的是,可能存在从起点开始,到1不足一轮的情况,所以如果起点为 4 或者 1 的时候,将答案数组赋值为0,其余情况赋值为-1(将半轮走过之后便为0了)。
所以还要把其余位置答案数组赋值为负无穷,才能用-1更新。
Code:
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;
const int N = 2010, mod = 1e9+7;
int T, n, m, k;
int a[N][N], f[N][N];
int dir[4][2]={{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int ru[N][N], cnt;
void topsort()
{
mem(f, -0x3f);
queue<PII> que;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(ru[i][j]) continue;
if(a[i][j] == 4 || a[i][j] == 1) f[i][j] = 0;
else f[i][j] = -1;
que.push({i, j});
}
while(que.size())
{
int x = que.front().fi, y = que.front().se;
cnt ++;
que.pop();
int t = a[x][y]-1; if(!t) t = 4;
for(int i=0;i<4;i++)
{
int tx = x+dir[i][0], ty = y+dir[i][1];
if(tx < 1 || ty < 1 || tx > n || ty > m) continue;
if(a[tx][ty] != t) continue;
if(a[tx][ty] == 1) f[tx][ty] = max(f[tx][ty], f[x][y]+1);
else f[tx][ty] = max(f[tx][ty], f[x][y]);
if(--ru[tx][ty] == 0) que.push({tx, ty});
}
}
}
signed main(){
Ios;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
char c;cin>>c;
if(c=='Q') a[i][j] = 1;
else if(c=='W') a[i][j]=2;
else if(c=='E') a[i][j]=3;
else a[i][j] = 4;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int t = a[i][j]-1; if(!t) t = 4;
for(int k=0;k<4;k++)
{
int tx = i+dir[k][0], ty = j+dir[k][1];
if(tx < 1 || ty < 1 || tx > n || ty > m) continue;
if(a[tx][ty] != t) continue;
ru[tx][ty] ++ ;
}
}
topsort();
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
// cout<<f[i][j]<<" ";
ans = max(ans, f[i][j]);
}
// cout<<endl;
}
if(cnt!=n*m) cout<<"infinity";
else if(!ans) cout<<"none";
else cout << ans;
return 0;
}
做法2:记忆化搜索
每走到一个点,如果这个点之前没搜过,就搜一遍,否则直接用答案。
对于每个点的更新,取所有可走点的答案的最大值将其更新。
如果当前点是4,那么将该点的答案+1,说明多了一轮了。
最后取每个1的答案的最大值。
而这种做法的难点在于,如何判环?
做法和普通 dfs 判环相同,如果走到了之前走过的路径,则说明出现了环。
所以递归之前标记当前点,递归之后恢复现场。如果发现要走的点标记了,说明走到之前走过的点了,也就是存在环了。
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;
const int N = 2010, mod = 1e9+7;
int T, n, m, k;
int a[N][N], p[N][N], f[N][N];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
bool st[N][N], flag;
int dfs(int x, int y)
{
int t = a[x][y]+1; if(t==5) t=1;
p[x][y] = 1;
int maxa=0;
for(int i=0;i<4;i++)
{
int tx = x+dir[i][0], ty = y+dir[i][1];
if(tx < 1 || ty < 1 || tx > n || ty > m) continue;
if(a[tx][ty] != t) continue;
if(st[tx][ty]) flag=1;
if(p[tx][ty]) maxa = max(maxa, f[tx][ty]);
else{
st[tx][ty] = 1;
maxa = max(maxa, dfs(tx, ty));
st[tx][ty] = 0;
}
}
f[x][y] = maxa;
if(a[x][y] == 4) f[x][y]++;
return f[x][y];
}
signed main(){
Ios;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
char c;cin>>c;
if(c=='Q') a[i][j] = 1;
else if(c=='W') a[i][j]=2;
else if(c=='E') a[i][j]=3;
else a[i][j] = 4;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i][j] == 1 && !p[i][j])
{
st[i][j] = 1;
dfs(i, j);
st[i][j] = 0;
}
}
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i][j] == 1) ans = max(ans, f[i][j]);
}
if(flag) cout<<"infinity";
else if(!ans) cout<<"none";
else cout << ans;
return 0;
}
运行效率比较
按理说两种做法每个点都只遍历了一次,复杂度差不多,但为啥最终的时间相差这么多呢?