行走路径——拓扑序dp / 记忆化搜索 + 判环

3828. 行走路径


题意:

给定一个 n*m 的矩阵,每个位置有一个数 x (1≤x≤4)。
规定只能按照 1 → 2 → 3 → 4 → 1 → 2 → . . . 1→2→3→4→1→2→... 123412... 的顺序行走,并且称 1 → 2 → 3 → 4 1→2→3→4 1234 为一轮。
问,在这个矩阵中,最多能走多少轮?如果出现能一直走下去,输出 -1。

1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000


做法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;
}

运行效率比较

在这里插入图片描述
按理说两种做法每个点都只遍历了一次,复杂度差不多,但为啥最终的时间相差这么多呢?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值