Codeforces 586D Phillip and Trains

本文介绍了一种通过深度优先搜索(DFS)和动态规划解决游戏中车辆移动问题的方法。该算法将车辆的移动转换为角色的移动,从而简化了问题。通过标记已访问的位置避免重复搜索,并最终判断车辆是否能到达目标位置。

题目链接


思路:将车动的两步,转换成人动两步。再进行DFS搜索记得标记


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;

int n,k; 
int sx,sy; 
char line[4][110]; 
bool flag;  
bool f[4][110]; 

void dfs( int x,int y ) {
	if( flag ) 
		return ;
	if ( y>=n-1 )	 {
		flag = true;   return ;
	}
	int tx,ty; 
	tx = x; 
	ty = y+1;
	
	if ( line[tx][ty]=='.' ) {
		for ( int i=0; i<3; i++ ) {
			tx = x+i-1; 
			if ( tx>=0 && tx<3 && line[tx][ty]=='.' ) {
				if ( (line[tx][ty+1]=='.' ||line[tx][ty+1]==0 )
				 && (line[tx][ty+2]=='.' ||line[tx][ty+2]==0 ) ) { 
				 	if (f[tx][ty+2]==false ) {
				 		f[tx][ty+2] = 1; 
				 		dfs(tx,ty+2); 	
					} 
					
				} 
					
			} 
		}
	} 
	return ;
} 
void init(int len) {
	for ( int i=0; i<3; i++ ) {
		for ( int j=0; j<len+5; j++ ) 
			line[i][j] = '.'; 
	}
	flag = false; 
	memset(f,false,sizeof(f)); 
}
int main (void) {
	int tests; 
	scanf("%d",&tests); 
	while ( tests-- ) {
		scanf("%d%d",&n,&k); 
		init(n); 
		for ( int i=0; i<3;i++ ) {
			scanf("%s",&line[i]); 
			for ( int j=0; j<n; j++ ) {
				if ( line[i][j]=='s' ) {
					sx = i; sy = j; 
				}
			}
		}	
		f[sx][sy] = 1; 
		dfs(sx,sy); 
		
		if ( flag ) cout<<"YES"<<endl; 
		else cout<<"NO"<<endl; 
	}
	return 0;
}



动态规划的做法:

dp[i][j] 代表第i层第j个位置能否走到

因为一下走四个位置,所以最后判断是要判断这四个位置。


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;

int n,k; 
int sx,sy; 
char line[5][110]; 
int dp[5][110]; 
bool flag; 

int main (void) {
	int tests; 
	scanf("%d",&tests); 
	while ( tests-- ) {
		scanf("%d%d",&n,&k); 
		memset(dp,0,sizeof(dp));
		for ( int i=1; i<n+5; i++ ) {
			for ( int j=1; j<=3; j++ ) 
				line[j][i] = '.'; 
		}
		flag = false;  
		
		for ( int i=1; i<=3;i++ ) {
			scanf("%s",&line[i]); 
			for ( int j=0; j<n; j++ ) {
				if ( line[i][j]=='s' ) {
					sx = i; sy = j; 
				}
			}
		}	
		dp[sx][sy] = 1; 
		
		for ( int j=3; j<=n+2; j+=3 ) 
	 	{
		 	for ( int i=1; i<=3; i++ ) {
		 		
				if ( ( line[i][j-1]=='.'|| line[i][j-1]=='\0' ) &&
					( line[i][j]=='.'   || line[i][j]=='\0' ) &&
					( line[i][j-2]=='.' || line[i][j-2]=='\0' ) 
					)  {
						
					if ( 
					(  dp[i][j-3] && ( line[i][j-2]=='.' ||line[i][j-2]=='\0' ) ) 
					||(dp[i-1][j-3] && ( line[i-1][j-2]=='.' ||line[i-1][j-2]=='\0' )) 
					||(dp[i+1][j-3] && ( line[i+1][j-2]=='.' ||line[i+1][j-2]=='\0' ))
					){	
						dp[i][j] = 1; 
					}
				}
			}
		}
		for ( int i=1; i<=3; i++ ) {
			for ( int j=n-1; j<=n+2; j++ ) {
				if ( dp[i][j] ) flag = 1; 
			}
		}
	
		if ( flag ) cout<<"YES"<<endl; 
		else cout<<"NO"<<endl; 
	}
	return 0;
}


<think>我们正在处理Codeforces 1775D题目的解析和解题思路。根据用户要求,我们需要参考站内引用,但注意到这些引用可能与题目并不直接相关。不过,我们可以从引用中提取一些思路,比如莫比乌斯反演(引用1提到gcd计数)和优化时间复杂度(引用2提到预处理和更新)的思想。 题目1775D是"Friends and the Restaurant",但根据Codeforces题库,1775D实际上是"Friendly Spiders"。因此,我们需要调整主题。 题目描述(摘要): - 有n个蜘蛛,编号1到n,每个蜘蛛的腿数为a_i。 - 两个蜘蛛i和j是朋友当且仅当gcd(a_i, a_j)>1(即它们的腿数的最大公约数大于1)。 - 需要从蜘蛛s到蜘蛛t,每一步只能移动到朋友蜘蛛,求最短路径(如果存在)。 解题思路: 1. 建图:直接对每对蜘蛛检查gcd会超时(O(n^2))。需要优化。 2. 思路:利用因子分解。对于每个蜘蛛,将其与它的质因数关联。然后,具有相同质因数的蜘蛛可以通过该质因数连接(因为如果两个蜘蛛共享一个质因数,那么它们之间的gcd至少为该质因数(>1))。 3. 具体步骤: a. 预处理每个数的质因数。 b. 创建一张图,包括n个蜘蛛节点和额外的质因数节点(为了避免重复,可以用质因数作为虚拟节点,编号从n+1开始)。 c. 对于每个蜘蛛i,将其与它的每个质因数p(用虚拟节点表示)连接一条无向边(双向边)。 d. 这样,两个蜘蛛如果有公共质因数,就会通过同一个虚拟节点相连(距离为2)。注意,蜘蛛节点之间不直接相连,而是通过质因数节点间接相连。 e. 在这样构建的图上,从s到t的最短路径(以边数计)除以2(因为实际路径中每两个蜘蛛之间经过一个虚拟节点,但虚拟节点不计入答案的路径节点)?注意:我们要求的是蜘蛛节点的路径,但路径中经过的虚拟节点不能出现在最终路径中,因此我们需要在BFS后重构路径。 4. 路径长度计算:由于图中加入了虚拟节点(质因数节点),实际路径长度为(真实路径的边数)/2(因为每两个蜘蛛节点之间隔着1个质因数节点,而质因数节点不算在最终路径里)。但注意,我们要求的是蜘蛛节点的序列,所以最终路径中的节点数 = (整个路径的节点数+1)//2(因为路径是蜘蛛-质因数-蜘蛛-...-蜘蛛,所以蜘蛛节点在奇数位置,且首尾都是蜘蛛)。 5. 具体步骤: - 使用BFS(因为边权为1)从s开始搜索,直到t。注意图中节点包括蜘蛛节点(1到n)和质因数节点(n+1到max_id)。我们需要记录每个节点的前驱节点,以便重构路径。 - 重构路径时,我们只取蜘蛛节点(即跳过质因数节点)。 6. 注意:如果s和t相同,则路径为0(但题目要求从s到t,如果s=t,那么路径长度为0,节点序列只包含s)。 7. 复杂度:每个蜘蛛连接它的质因数(每个数的质因数个数为O(log a_i)),所以总边数O(n*log(max(a_i))),可以接受。 8. 代码实现关键点: - 质因数分解:预处理每个数的质因数(可以不用筛,直接分解)。 - 建图:图的大小为n+质因数节点总数(质因数节点只需要出现一次,所以用一个map来记录质因数对应的虚拟节点编号)。 - BFS:注意节点编号范围。 现在,我们根据上述思路编写解析和代码。 注意:由于虚拟节点的存在,在BFS中,我们记录路径时,虚拟节点不放入最终答案路径。因此,当我们回溯路径时,只保留蜘蛛节点(编号在1到n之间)。 例子:假设蜘蛛1(质因数2,3)和蜘蛛2(质因数2,5)和蜘蛛3(质因数3,5)。 建图: - 蜘蛛1连接质因数2和3。 - 蜘蛛2连接质因数2和5。 - 蜘蛛3连接质因数3和5。 那么蜘蛛1到蜘蛛3的路径:1->2->2(蜘蛛2)->5->3(蜘蛛3)?不对,实际上通过质因数节点: 路径:蜘蛛1 -> 质因数2 -> 蜘蛛2 -> 质因数5 -> 蜘蛛3(这不是最短的,因为还有另一条:蜘蛛1->质因数3->蜘蛛3,更短)。 在图上BFS:从1(蜘蛛1)出发,到达质因数2和3,然后从质因数2可以到蜘蛛2,从质因数3可以到蜘蛛3。所以从1到3的路径:1 -> 质因数3 -> 3(蜘蛛3)。然后重构路径时,我们只取蜘蛛节点:1和3,所以路径长度为1(边数)?不对,实际路径是1-3(中间有一个虚拟节点,但最终路径只包含1和3,所以边数为1,即1->3,但实际上它们不是直接朋友,但通过质因数3连接,所以是朋友关系,路径长度为1步)。 所以最终路径的边数等于(BFS得到的路径边数)除以2?不对,因为BFS路径中每一步都是从一个节点到另一个节点(包括虚拟节点)。例如:1->质因数3->3,这有两条边,但实际蜘蛛节点之间只经过一步(从1到3)。所以最终路径的节点数应该是BFS路径中蜘蛛节点的序列。 在BFS过程中,我们记录每个节点的前驱。当我们到达终点t(蜘蛛节点)时,我们回溯前驱,但跳过虚拟节点。然而,虚拟节点在路径中连接两个蜘蛛节点,所以回溯时,我们每遇到一个虚拟节点,它的前驱和后继都是蜘蛛节点。因此,我们可以这样重构: 路径:t -> 前一个节点(可能是虚拟节点)-> 再前一个节点(蜘蛛节点)-> ... -> s 我们只取蜘蛛节点,并反转。 具体步骤: 1. 从t开始回溯,将t加入路径。 2. 当前节点为t,它的前驱节点记为prev。如果prev是虚拟节点(编号>n),那么继续找prev的前驱(应该是蜘蛛节点)。所以每次回溯一个蜘蛛节点后,下一个节点是虚拟节点,再下一个节点是蜘蛛节点。因此,我们每次回溯两步来跳过虚拟节点?不对,因为虚拟节点和蜘蛛节点交替出现(除了起点和终点是蜘蛛节点)。 实际上,在BFS树中,路径是:s -> p1(质因数节点)-> u1(蜘蛛节点)-> p2(质因数节点)-> u2(蜘蛛节点)-> ... -> t 所以,从t回溯:t的前驱是一个质因数节点,质因数节点的前驱是一个蜘蛛节点(即路径中t的前一个蜘蛛节点)。因此,我们可以: path = [] cur = t while cur != s: path.append(cur) cur = pre[cur] # pre[cur]是当前节点的前驱(在BFS树中) # 此时cur是虚拟节点,那么再取一次前驱得到上一个蜘蛛节点 cur = pre[cur] # 现在cur是上一个蜘蛛节点 path.append(s) path.reverse() 但是注意:从s到第一个虚拟节点,s的前驱是空(因为s是起点)。所以我们需要调整:在BFS过程中,我们记录每个节点的前驱(包括虚拟节点)。然后重构路径时,我们跳过虚拟节点。 另一种方法:在BFS过程中,我们记录每个节点的前驱节点(无论是蜘蛛还是质因数)。然后重构路径时,我们只取蜘蛛节点。那么从t开始,将t加入路径,然后找t的前驱(一定是虚拟节点),再找这个虚拟节点的前驱(一定是蜘蛛节点),将这个蜘蛛节点加入路径,然后继续找这个蜘蛛节点的前驱(虚拟节点)...直到到达s。 步骤: path = [] cur = t while cur is not None: if cur <= n: # 蜘蛛节点 path.append(cur) cur = pre[cur] # 移动到前驱(可能是虚拟节点或None) # 这样得到的是从t到s的路径(逆序),然后反转 path.reverse() 但是这样会包括虚拟节点吗?注意:我们在加入路径时只加入蜘蛛节点(cur<=n),所以虚拟节点被跳过。但是,我们回溯时,每次从蜘蛛节点找到它的前驱(虚拟节点)然后继续再找前驱(上一个蜘蛛节点)?不对,因为pre[t]是一个虚拟节点,然后pre[虚拟节点]是一个蜘蛛节点(即路径中t的前一个蜘蛛节点),然后我们加入这个蜘蛛节点,然后找这个蜘蛛节点的前驱(另一个虚拟节点)...直到s。 所以循环内部: 第一次:cur=t(蜘蛛节点)->加入 然后cur = pre[t] (虚拟节点,不加入) 然后cur = pre[虚拟节点] (上一个蜘蛛节点,在下一次循环加入) 这样会交替加入蜘蛛节点。所以最终路径中只有蜘蛛节点,且顺序正确。 但是,注意起点s:当回溯到s时,s是蜘蛛节点,加入。然后找pre[s],如果s是起点,那么pre[s]应该是None,循环终止。 因此,代码: path = [] cur = t while cur is not None: if cur <= n: path.append(cur) cur = pre[cur] # 移动到前驱(如果cur是s,pre[s]为None,则退出) path.reverse() 这样得到的路径就是蜘蛛节点的序列。 但是,我们如何保证pre[虚拟节点]一定指向蜘蛛节点?在建图时,我们只建立了蜘蛛节点与质因数节点之间的边,没有质因数节点到质因数节点的边。所以图中边只存在于蜘蛛节点和质因数节点之间。因此,在BFS树中,路径一定是交替的:蜘蛛-质因数-蜘蛛-质因数-...-蜘蛛。 因此,上述重构路径方法是正确的。 9. 特殊情况:s==t,则路径只有[s];如果没有路径,则输出-1。 代码实现(Python)可能如下: </think> ### Codeforces 1775D - Friendly Spiders 解析 #### 题目概述 有 $n$ 只蜘蛛,每只蜘蛛 $i$ 有腿数 $a_i$。两只蜘蛛 $i$ 和 $j$ 是“朋友”当且仅当 $\gcd(a_i, a_j) > 1$。给定起点 $s$ 和终点 $t$,求从 $s$ 到 $t$ 的最短路径(每次移动到朋友蜘蛛),并输出路径。若不可达,返回 $-1$。 #### 核心难点 直接暴力建图(检查每对蜘蛛的 $\gcd$)的复杂度为 $O(n^2)$,无法通过 $n \leq 3 \times 10^5$ 的数据规模。 #### 优化思路:质因数分解建图 1. **关键观察**: - 若两只蜘蛛有公共质因数 $p$,则 $\gcd(a_i, a_j) \geq p > 1$,它们可通过 $p$ 间接相连。 - 为每个质因数 $p$ 创建**虚拟节点**(编号从 $n+1$ 开始),蜘蛛 $i$ 与其所有质因数 $p \mid a_i$ 连边。 2. **建图策略**: - **真实节点**:蜘蛛 $1$ 到 $n$。 - **虚拟节点**:质因数 $p$(用 $\text{id}_p \geq n+1$ 表示)。 - **连边规则**: - 蜘蛛 $i$ 连接其所有质因数虚拟节点 $\text{id}_p$(双向边)。 - 例:蜘蛛 $a_i=12$(质因数 $2,3$)连向 $\text{id}_2$ 和 $\text{id}_3$。 3. **路径性质**: - 路径格式:蜘蛛 $\to$ 质因数 $\to$ 蜘蛛 $\to \cdots \to$ 蜘蛛。 - 实际路径长度 = BFS 路径边数 $/ 2$(虚拟节点不计入最终路径)。 #### 算法步骤 1. **预处理质因数**: - 对每个 $a_i$ 分解质因数,用字典记录质因数对应的虚拟节点编号。 2. **BFS 求最短路**: - 从 $s$ 开始 BFS,记录每个节点的前驱 $\text{pre}[u]$。 - 队列同时处理真实节点和虚拟节点。 3. **路径重构**: - 从 $t$ 回溯前驱,只保留真实节点(跳过虚拟节点)。 - 回溯逻辑:若当前是真实节点,加入路径;无论何种节点,均回溯到前驱。 #### 复杂度分析 - **时间**:$O(n \log \max(a_i))$,每个数的质因数分解 $O(\log a_i)$。 - **空间**:$O(n \log \max(a_i))$,存储图及质因数。 #### 代码实现(Python) ```python import math from collections import deque def main(): import sys input = sys.stdin.readline n = int(input()) a = [0] + list(map(int, input().split())) s, t = map(int, input().split()) if s == t: print("1\n", s) return # 质因数虚拟节点编号分配 max_id = n prime_to_id = {} graph = [[] for _ in range(4 * 10 ** 5)] # 预分配空间 # 建图:连接蜘蛛与其质因数 for i in range(1, n + 1): x = a[i] factors = [] # 质因数分解 p = 2 while p * p <= x: if x % p == 0: factors.append(p) while x % p == 0: x //= p p += 1 if x > 1: factors.append(x) for p in factors: if p not in prime_to_id: max_id += 1 prime_to_id[p] = max_id pid = prime_to_id[p] # 双向边:蜘蛛 <-> 质因数 graph[i].append(pid) graph[pid].append(i) # BFS 初始化 pre = [-1] * (max_id + 1) # 前驱数组 q = deque([s]) pre[s] = -2 # 起点标记 # BFS 遍历 while q: u = q.popleft() for v in graph[u]: if pre[v] == -1: # 未访问 pre[v] = u if v == t: q = None # 提前终止 break q.append(v) # 检查是否可达 if pre[t] == -1: print(-1) return # 重构路径(跳过虚拟节点) path = [] cur = t while cur != -2: # 回溯到起点标记 if cur <= n: # 只保留真实节点 path.append(cur) cur = pre[cur] # 输出结果 print(len(path)) print(' '.join(map(str, reversed(path)))) if __name__ == '__main__': main() ``` #### 示例说明 - **输入**:蜘蛛 $a=[2,3,4,5,6]$,$s=1, t=5$。 - **建图**: - 蜘蛛 $1$($a_1=2$)连虚拟节点 $\text{id}_2$。 - 蜘蛛 $5$($a_5=6$)连 $\text{id}_2,\text{id}_3$。 - **路径**:$1 \to \text{id}_2 \to 5$(重构后为 $[1,5]$)。 - **输出**:路径长度 $2$,路径 $1 \space 5$。 #### 相关思考 1. 若存在多条最短路径,如何输出字典序最小路径? 2. 如何进一步优化质因数分解的效率? 3. 若蜘蛛可重复访问,算法如何调整?[^1] [^1]: 解题报告(十八)数论题目泛做(Codeforces 难度:2000 ~ 3000 + ) [^2]: 如果对于每个查询q都找到不同gcd的递减个数,时间复杂度为O(n*q)会超时 [^3]: 如果看到这里,你还没有找到规律,那我们就在看一组数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值