P1137 旅行计划

原题链接:旅行计划 - 洛谷

题目描述

小明要去一个国家旅游。这个国家有 NN 个城市,编号为 11 至 NN,并且有 MM 条道路连接着,小明准备从其中一个城市出发,并只往东走到城市 ii 停止。

所以他就需要选择最先到达的城市,并制定一条路线以城市 ii 为终点,使得线路上除了第一个城市,每个城市都在路线前一个城市东面,并且满足这个前提下还希望游览的城市尽量多。

现在,你只知道每一条道路所连接的两个城市的相对位置关系,但并不知道所有城市具体的位置。现在对于所有的 ii,都需要你为小明制定一条路线,并求出以城市 ii 为终点最多能够游览多少个城市。

输入格式

第一行为两个正整数 N, MN,M。

接下来 MM 行,每行两个正整数 x, yx,y,表示了有一条连接城市 xx 与城市 yy 的道路,保证了城市 xx 在城市 yy 西面。

输出格式

NN 行,第 ii 行包含一个正整数,表示以第 ii 个城市为终点最多能游览多少个城市。

输入输出样例

输入 #1复制

5 6
1 2
1 3
2 3
2 4
3 4
2 5

输出 #1复制

1
2
3
4
3

说明/提示

均选择从城市 11 出发可以得到以上答案。

  • 对于 20\%20% 的数据,1\le N ≤ 1001≤N≤100;
  • 对于 60\%60% 的数据,1\le N ≤ 10001≤N≤1000;
  • 对于 100\%100% 的数据,1\le N ≤ 1000001≤N≤100000,1\le M ≤ 2000001≤M≤20000000
  • 代码如下:
  • #include<bits/stdc++.h>
    using namespace std;
    struct D{
    	int nxt,to;
    }d[200001];
    int l[200005],w,n,m,a,b,h[200005],v[200005];
    queue<int>q;
    void xx(int x,int y){
    	d[++w].nxt=h[x]; 
    	d[w].to=y;
    	h[x]=w;
    }
    int main(){
    	cin>>n>>m;
    	for(int i=1;i<=m;i++){
    		cin>>a>>b;
    		xx(a,b);
    		l[b]++;
    	}
    	for(int i=1;i<=n;i++){
    		if(!l[i]){
    			q.push(i); 
    			v[i]=1; 
    		}
    	}
    	while(!q.empty()){
    		int x1=q.front();
    		for(int i=h[x1];i!=0;i=d[i].nxt){
    			l[d[i].to]--; 
    			if(!l[d[i].to]){
    				v[d[i].to]=v[x1]+1;
    				q.push(d[i].to); 
    			}
    		}
    		q.pop();
    	}
    	for(int i=1;i<=n;i++){
    		printf("%d\n",v[i]);
    	}
    	return 0;
    }

<think>好的,用户想解决洛谷P1137的拓扑排序问题,需要C++的解法。首先,我得回忆一下拓扑排序的基本概念和应用场景。拓扑排序主要用于有向无环图(DAG)中,用来确定节点之间的线性顺序,满足所有的有向边从前往后的方向。这在任务调度、依赖关系处理中很常见。 接下来,用户提到的是模板题,所以可能题目要求的是标准的拓扑排序实现。根据引用[4]中的例子,拓扑排序的实现通常需要维护入度数组,并使用队列来处理节点。我应该先考虑如何构建图结构,通常用邻接表比较高效。 然后,用户可能希望得到具体的代码实现。根据引用[2]和[4]中的内容,C++实现拓扑排序一般会用队列(或者优先队列,如果需要特定顺序),这里可能需要用STL中的queue。步骤大致是:构建邻接表,记录每个节点的入度,将入度为0的节点入队,然后依次处理,减少相邻节点的入度,若入度为0则入队,同时记录拓扑顺序。 接下来,我需要考虑题目P1137的具体要求。可能每个节点有一个权重,或者需要计算最长路径之类的,因为拓扑排序常被用来处理关键路径或最长路径问题。比如,在P1137中,题目可能要求求出每个节点的最长路径长度,这在拓扑排序过程中可以通过动态规划的方式逐步更新每个节点的最大值。 假设题目是这样的,那么步骤应该是:在拓扑排序的过程中,维护一个距离数组,初始化为节点的初始值,然后对于每个出队的节点u,遍历其所有邻接节点v,更新v的距离为max(当前值, u的距离 + 边的权重)。这样,当拓扑排序完成后,每个节点的距离就是最长路径的长度。 现在,我需要验证这个思路是否正确。比如,引用[4]中的例子,拓扑序列的正确性依赖于入度处理。同时,引用[2]提到Dijkstra算法,但这里的问题可能不需要最短路径,而是基于拓扑排序的最长路径,所以动态规划的方法更合适。 然后,编写代码的结构:首先读取输入,构建邻接表和入度数组,初始化队列,处理入度为0的节点,然后进行拓扑排序,并在过程中更新最长路径。需要注意可能的初始化问题,比如每个节点的初始距离是否为1(如果题目中每个节点自身算作一个单位长度)。 例如,如果题目中每个节点自身代表一个点权为1,那么初始距离数组应为1,每次更新时加上邻接边的权值,但如果是边权,则可能需要调整。需要根据题目具体要求调整初始化和更新的逻辑。 此外,还需要处理可能的多个测试用例或者输入格式的问题。比如,节点数、边数,以及每条边的输入方式。在C++中,使用vector来构建邻接表比较方便,入度数组用数组或vector存储,队列用queue<int>。 最后,测试代码是否正确。比如,对于引用[4]中的例子,拓扑序列是否正确生成,最长路径的计算是否符合预期。例如,如果有三个节点1→2→3,边权均为1,那么最长路径长度应该是3,每个节点依次累加。 总结下来,解题步骤是:构建图,拓扑排序,动态规划更新最长距离。代码需要包括这些部分,并处理好输入输出。</think>### 洛谷 P1137 旅行计划 题解 #### 问题分析 题目要求根据城市之间的单向路线,计算从首都(固定为1号城市)出发访问每个城市最多能经过的城市数量。这等价于在**有向无环图(DAG)**中,以1号节点为起点,求每个节点的**最长路径**长度。 #### 核心算法:拓扑排序 + 动态规划 1. **拓扑排序**确保按照依赖关系处理节点顺序 2. **动态规划**记录每个节点的最长路径长度: $$dp[v] = \max(dp[v], dp[u] + 1)$$ #### 具体步骤 1. **建图**:邻接表存储有向边 2. **拓扑排序**:通过队列实现,记录节点处理顺序 3. **路径计算**:按拓扑序更新每个节点的最大访问数 #### C++代码实现 ```cpp #include <iostream> #include <vector> #include <queue> using namespace std; const int MAXN = 1e5 + 5; vector<int> adj[MAXN]; int inDegree[MAXN], dp[MAXN], n, m; void topological_sort() { queue<int> q; for (int i = 1; i <= n; ++i) { if (inDegree[i] == 0) q.push(i); } while (!q.empty()) { int u = q.front(); q.pop(); for (int v : adj[u]) { dp[v] = max(dp[v], dp[u] + 1); // 动态规划更新最长路径 if (--inDegree[v] == 0) { q.push(v); } } } } int main() { cin >> n >> m; for (int i = 1; i <= n; ++i) dp[i] = 1; // 每个城市至少访问自己 while (m--) { int u, v; cin >> u >> v; adj[u].push_back(v); inDegree[v]++; } topological_sort(); for (int i = 1; i <= n; ++i) { cout << dp[i] << endl; } return 0; } ``` #### 代码说明 - **dp数组**:存储每个节点的最长路径长度,初始化为1(包含自身) - **拓扑排序**:处理节点时更新后续节点的最长路径 - **时间复杂度**:$O(N + M)$,满足题目要求 #### 注意事项 1. 确保输入为DAG(题目保证无环) 2. 动态规划更新顺序必须遵循拓扑序 3. 输出时需要换行符分隔结果 #### 性能优化方向 1. 使用前向星存图减少内存占用 2. 并行处理多个入度为0的节点(不影响结果正确性) 3. 使用数组模拟队列提升速度 拓扑排序在图论问题中广泛应用,特别是在处理依赖关系时具有独特优势[^4]。本题通过拓扑序保证动态规划的正确性,是经典算法组合应用的典型案例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值