1 条题解
-
0
题意分析:
农夫罗恩要在宽 (W) 、长 (L) 的矩形区域建私人滑雪区,每个土地方格有海拔高度 (H) 。
奶牛滑雪规则为:可水平或垂直在相邻方格滑行,从高到低单向滑,相同高度可双向滑。
滑雪缆车可在任意两方格间建,双向且可交叉,为使奶牛能在任意方格间通行,要找最少缆车数量。输入为滑雪区宽 (W) 、长 (L) 以及每个方格的高度。
解题思路:
1.将每个方格看作图的节点,若满足滑雪条件(高度关系允许滑行),则在对应节点间建边。
2.使用 Tarjan 算法找出图中的强连通分量,每个强连通分量可看作一个“连通块”,在块内奶牛无需缆车可自由通行。
3.对于不同强连通分量,统计每个强连通分量的入度 (in) 和出度 (out) 。
4.最少的滑雪缆车数量等于入度为 0 的强连通分量数量和出度为 0 的强连通分量数量中的较大值。若只有一个强连通分量((id = 1)),则不需要缆车,输出 0 。
分析:
1.时间复杂度:构建图的时间复杂度为 (O(n × m)) ((n) 为行数,(m) 为列数),Tarjan 算法的时间复杂度为 (O(V + E)) ,其中 (V) 是节点数((V = n × m) ),(E) 是边数((E) 最大为 (4 × n × m) ),统计入度和出度的时间复杂度为 (O(E)) 。所以总的时间复杂度为 (O(n × m)) 。
2.空间复杂度:使用了邻接表存储图结构((e) 数组、(head) 数组)、记录强连通分量信息的数组((dfn) 、(low) 、(co) )、入度和出度数组((in) 、(out) )等,空间复杂度为 (O(n × m)) 。
实现步骤:
1.初始化:清空栈,初始化各种数组((in) 、(out) 、(co) 、(head) 、(dfn) 、(low) ),设置计数器 (cnt) 、强连通分量编号 (id) 、时间戳 (tot) 为 0 。
2.输入处理:读取滑雪区的宽 (W) 、长 (L) 以及每个方格的高度。
3.图的构建:遍历每个方格,根据滑雪规则在满足条件的相邻方格对应节点间建边(调用 (add) 函数)。
4.强连通分量求解:对每个未访问的节点调用 Tarjan 算法,找出强连通分量并标记((co) 数组记录每个节点所属的强连通分量)。
5.统计入度和出度:遍历图的边,若边连接的两个节点属于不同强连通分量,则更新对应强连通分量的入度和出度。
6.计算结果:统计入度为 0 和出度为 0 的强连通分量数量 (x) 和 (y) ,输出 (max(x, y)) ;若只有一个强连通分量((id = 1)),输出 0 。
代码实现
#include <cstdio> #include<iostream> #include <cstring> #include <algorithm> #include <stack> using namespace std; #define ll long long const int maxm=1000; const int maxn=1e6; stack<int>q;int mp[maxm][maxm]; int cnt,n,m,head[maxn],dfn[maxn],low[maxn],co[maxn],tot,id,out[maxn],in[maxn]; struct Edge { int to,next; }e[maxn*10]; void add(int u,int v) { e[cnt].to=v; e[cnt].next=head[u]; head[u]=cnt++; } void init() { memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); memset(co,0,sizeof(co)); memset(head,-1,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); while(!q.empty()) q.pop(); cnt=0;id=0,tot=0; } void tarjan(int u) { q.push(u); dfn[u]=low[u]=++tot; for(int i=head[u];i!=-1;i=e[i].next) { int v=e[i].to; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(!co[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { id++; while(q.top()!=u) { int x=q.top(); q.pop(); co[x]=id; } co[u]=id; q.pop(); } } int js(int i,int j) { return (i-1)*m+j; } int main() { while(scanf("%d %d",&m,&n)!=EOF) { init(); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { scanf("%d",&mp[i][j]); } } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { //printf("js(%d %d)=%d js(%d %d)=%d js(%d %d)=%d js(%d %d)=%d\n",i-1,j,js(i-1,j),i+1,j,js(i+1,j),i,j-1,js(i,j-1),i,j+1,js(i,j+1)); if(i>1&&mp[i][j]>=mp[i-1][j]) add(js(i,j),js(i-1,j)); if(i<n&&mp[i][j]>=mp[i+1][j]) add(js(i,j),js(i+1,j)); if(j>1&&mp[i][j]>=mp[i][j-1]) add(js(i,j),js(i,j-1)); if(j<m&&mp[i][j]>=mp[i][j+1]) add(js(i,j),js(i,j+1)); } } int ed=n*m; for(int i=1;i<=ed;i++) { if(dfn[i]==0) { tarjan(i); } } if(id==1) printf("0\n"); else { for(int i=1;i<=ed;i++) { for(int j=head[i];j!=-1;j=e[j].next) { int v=e[j].to; if(co[i]!=co[v]) { //printf("i=%d j=%d u=%d v=%d\n",i,v,co[i],co[v]); out[co[i]]++; in[co[v]]++; } } } int x=0,y=0; for(int i=1;i<=id;i++) { //printf("out[%d]=%d in[%d]=%d\n",i,out[i],i,in[i]); if(out[i]==0) x++; if(in[i]==0) y++; } printf("%d\n",max(x,y)); } } }
代码说明:
1.头文件和命名空间:包含了
<cstdio>
用于输入输出操作,<iostream>
用于输入输出流,<cstring>
用于字符串和数组初始化操作,<algorithm>
用于算法相关操作(如min
函数),<stack>
用于栈操作。使用using namespace std;
使代码可以直接使用标准命名空间中的函数和类型。2.宏定义和常量定义:
#define ll long long
定义ll
为long long
类型。const int maxm = 1000;
和const int maxn = 1e6;
定义了一些数组大小的上限。3.变量定义:
mp[maxm][maxm]
存储每个方格的高度;q
是用于 Tarjan 算法的栈;cnt
用于边的计数,n
和m
分别表示滑雪区的长和宽,head[maxn]
是邻接表表头数组,dfn[maxn]
和low[maxn]
用于 Tarjan 算法记录时间戳和最小时间戳,co[maxn]
记录每个节点所属的强连通分量编号,tot
是时间戳,id
是强连通分量编号,out[maxn]
和in[maxn]
分别记录强连通分量的出度和入度。4.结构体定义:
Edge
结构体定义了边的信息,包括目标节点to
和下一条边的编号next
。5.函数定义: -
add
函数用于向邻接表中添加边。 -init
函数用于初始化各种变量和数组。 -tarjan
函数实现了 Tarjan 算法,用于找出强连通分量。 -js
函数将二维坐标转换为一维节点编号。6.主函数: - 通过
while(scanf("%d %d", &m, &n) != EOF)
循环读取输入数据。 - 每次输入前调用init
函数初始化。 - 读取每个方格的高度并构建图。 - 对每个未访问节点调用tarjan
函数找出强连通分量。 - 根据强连通分量信息统计入度和出度。 - 根据入度和出度情况输出最少滑雪缆车数量。
- 1
信息
- ID
- 1376
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者