POJ 1915 Knight Moves

本文介绍了一道经典的广度优先搜索(BFS)题目,通过实现一个程序来计算国际象棋中骑士从一个位置移动到另一个位置所需的最少步数。文章提供了完整的C++代码示例,并解释了解题思路。

http://poj.org/problem?id=1915

一道不错的基础广搜题目,记得很久以前接触的搜索,当时只是浅浅的接触了一下,不过很快就忘了,貌似chl也不太熟,以至于之前的几次比赛,一遇到搜索的题目我就大哭了,幸亏我们队还有飘飘。。搜索是图论的基础,一定要学好奋斗

背景:神话般的国际象棋玩家Somurolov先生,他声称,他可以把一个骑士从一个位置很快地移动到另一个位置,但其他人却不行。你能打败他吗?

  存在的问题:你的任务是编写一个程序来计算骑士达到从另一个位置所要移动的最少步数,这样你才有机会比Somurolov快。也许人们不熟悉的国际象棋,骑士行动可能在如图1所示。


  输入:首先在第一个行,输入n,表示有n种的情况。 接下来是n方案。每个方案包括三行。第一行指定一个棋盘边长L(4<=L<= 300)。整个棋盘的尺寸L*L.第二和第三行包含整数对(0,...,L - 1)*(0,...,L - 1)指定骑士开始和结束位置。整数对由一个空白分隔开。

  输出:对于每个输入你要计算骑士的行动,有必要从起点移动到终点最少步数的情况。如果起点和终点都是平等的,距离是零。


解题思路:题目其实就是(在满足骑士走法的条件下)求起点到终点的最短路,而广搜是一层一层的搜,这样如果从起点开始,一层一层的扩展,只要到达终点所用的步数就是最少的。

#include<stdio.h>
#include<string.h>
#define N 305
bool m[N][N];
int fx[8][2]=          // 方向数组
{{-2,-1},{-1,-2},{1,-2},{2,-1},
{2,1},{1,2},{-1,2},{-2,1}};
 struct stu        //结点信息
{
	int x,y,step;
	bool operator ==(stu t)   //重载运算符“==”
	{
		return t.x==x&&t.y==y;
	}
}S,T,queue[N*N];//起点,终点,队列
int n;           //图的大小
int bfs()
{
	int front,end,i;
	stu s,t;
	front=end=0;       //初始化队列
	queue[end++]=S;    //起点入队
	memset(m,false,sizeof(m));  //标记数组初始化
	while(front<end)           //判断队列是否为空
	{
		s=queue[front++];        //出队
		if(s==T) return s.step;	  //判断是否到达终点
		for(i=0;i<8;i++)        //八个方向
		{
			t.x=s.x+fx[i][0];
			t.y=s.y+fx[i][1];
			t.step=s.step+1;
			if(t.x<0||t.y<0||t.x>=n||t.y>=n)  //判断是否出界
				continue;
			if(m[t.x][t.y]) continue;     //判断之前是否已经到达过
			queue[end++]=t;              //入队
			m[t.x][t.y]=true;          //标记
		}
	}
	return -1;
}
int main()
{
	int p;
	scanf("%d",&p);
	while(p--)
	{
		scanf("%d",&n);
		scanf("%d%d%d%d",&S.x,&S.y,&T.x,&T.y);
		printf("%d\n",bfs());
	}
	return 0;
}



### POJ1915 解题思路 POJ1915 是关于图论中的强连通分量问题,主要涉及 Tarjan 算法的应用以及如何通过缩点使有向无环图(DAG)变为强连通图。以下是该问题的核心算法思路: #### 问题描述 题目要求计算在一个有向图中,最少需要添加多少条边才能使得整个图成为强连通图。 --- #### 核心算法分析 1. **Tarjan 缩点** 使用 Tarjan 算法找到所有的强连通分量并将其缩成单个节点,从而得到一个新的有向无环图(DAG)。此过程可以有效简化原始图结构[^2]。 2. **统计入度和出度为零的节点数** 对于新的 DAG 图,分别统计入度为零的节点数量 \(in\_zero\) 和出度为零的节点数量 \(out\_zero\)。这两个值决定了需要增加的边的数量。 3. **计算所需新增边数** 新增边的数量取决于以下两种情况的最大值: \[ answer = \max(in\_zero, out\_zero) \] 这是因为每一条新加入的边都可以消除一个入度或出度为零的情况。 4. **特殊情况处理** 如果初始图已经是强连通图,则无需任何操作,即答案为 0。 --- #### 实现细节 下面提供了一个基于 C++ 的实现框架,展示了如何利用 Tarjan 算法解决这个问题: ```cpp #include <iostream> #include <vector> #include <stack> using namespace std; const int MAXN = 1e4 + 5; int n, m, idx, scc_cnt; int dfn[MAXN], low[MAXN]; bool in_stack[MAXN]; vector<int> adj[MAXN]; stack<int> stk; vector<vector<int>> SCC; // 存储每个强连通分量 void tarjan(int u) { dfn[u] = low[u] = ++idx; stk.push(u); in_stack[u] = true; for (auto &v : adj[u]) { if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if (in_stack[v]) { low[u] = min(low[u], dfn[v]); } } if (low[u] == dfn[u]) { vector<int> component; while (true) { int t = stk.top(); stk.pop(); in_stack[t] = false; component.push_back(t); if (t == u) break; } SCC.push_back(component); scc_cnt++; } } // 主函数逻辑 int main() { cin >> n >> m; for (int i = 0; i < m; ++i) { int a, b; cin >> a >> b; adj[a].push_back(b); } // 初始化 Tarjan 参数 memset(dfn, 0, sizeof(dfn)); memset(in_stack, false, sizeof(in_stack)); idx = scc_cnt = 0; // 执行 Tarjan 算法 for (int i = 1; i <= n; ++i) { if (!dfn[i]) tarjan(i); } // 构建 DAG 并统计出入度 int indegree_zero = 0, outdegree_zero = 0; vector<int> dag_in(scc_cnt + 1, 0), dag_out(scc_cnt + 1, 0); for (int u = 1; u <= n; ++u) { for (auto &v : adj[u]) { if (SCC[dfn[u]].back() != SCC[dfn[v]].back()) { // 不同强连通分量之间才有边 dag_in[SCC[dfn[v]].back()]++; dag_out[SCC[dfn[u]].back()]++; } } } for (int i = 1; i <= scc_cnt; ++i) { if (dag_in[i] == 0) indegree_zero++; if (dag_out[i] == 0) outdegree_zero++; } cout << max(indegree_zero, outdegree_zero); // 输出结果 } ``` --- #### 关键点解释 - **Tarjan 算法的作用** Tarjan 算法能够高效地找出所有强连通分量,并将它们压缩成单一节点,便于后续的操作[^2]。 - **为什么取最大值?** 添加一条边最多能减少一个入度为零或者出度为零的节点。因此,最终的结果应为两者之间的较大者。 - **时间复杂度** 整体的时间复杂度为 \(O(n+m)\),其中 \(n\) 表示节点数,\(m\) 表示边数。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值