UVa 12717 Fiasco

发布时间:2026/10/5 2:12:22
UVa 12717 Fiasco 题目描述Natasha\texttt{Natasha}Natasha在算法课上总是混淆概念。上周Chhaya Murthy\texttt{Chhaya Murthy}Chhaya Murthy教授布置了一个经典问题在加权图中从指定源点出发计算到所有节点的最短路径。而在此之前教授讲授了Prim\texttt{Prim}Prim算法和Dijkstra\texttt{Dijkstra}Dijkstra算法它们看起来相似但功能截然不同。Natasha\texttt{Natasha}Natasha被搞糊涂了她提交了一个与Prim\texttt{Prim}Prim算法非常相似的代码我们称之为Natasha\texttt{Natasha}Natasha算法并且没有充分测试。她的伪代码如下functionShortest(Graph,source):foreach vertex v in Graph:visited[v]:false;dist[v]:infinity;previous[v]:undefined;ans[v]:undefined;endfordist[source]:0;Q:{}// priority queue, pop gives node with smallest dist, tie by smallest idPush(source,dist[source])to QwhileQ isnotempty:Pop node u from Q;visited[u]:true;ifdist[u]infinity:break;foreach neighbor v of u:ifvisited[v]true:continue;alt:edge_cost(u,v);ifaltdist[v]:dist[v]:alt;previous[v]:u;Push(v,dist[v])to Q;endforendwhileforeach node node in Graph:answer:0;u:node;whileprevious[u]is defined:answer:answeredge_cost(u,previous[u]);u:previous[u];endwhileans[node]:answer;returnans;endfunction提交后她的朋友发现了漏洞。为了帮助她Rehan\texttt{Rehan}Rehan要求她不要改变边连接的顶点也不要改变边权重的整体集合她只能重新排列哪些权重分配给哪些边。请帮助她找到一种边权重分配方案使得Natasha\texttt{Natasha}Natasha的算法在重新分配后的图上能够正确输出从源点到所有节点的最短路径。输入格式第一行包含测试用例数TTT1≤T≤151 \le T \le 151≤T≤15。每个测试用例的第一行包含三个整数n,m,sourcen, m, sourcen,m,source2≤n≤25002 \le n \le 25002≤n≤25001≤m≤250001 \le m \le 250001≤m≤250001≤source≤n1 \le source \le n1≤source≤n分别表示节点数、边数和源点编号。接下来mmm行每行三个整数u,v,wu, v, wu,v,w表示节点uuu和vvv之间有一条权重为www的边1≤w≤m1 \le w \le m1≤w≤m。保证图中没有重边或自环图是连通的且所有边的权重互不相同。输出格式对于每个测试用例首先输出一行Case X其中XXX是测试用例编号。然后按输入顺序输出每条边的三个整数u,v,wu, v, wu,v,w每个三元组占一行用空格分隔。如果有多个可行解输出任意一个。样例输入1 7 9 5 2 4 2 1 4 8 7 2 6 3 4 7 5 7 5 7 3 9 6 1 1 6 3 4 5 6 3输出Case 1: 2 4 7 1 4 8 7 2 3 3 4 9 5 7 1 7 3 4 6 1 5 6 3 6 5 6 2题目分析Natasha\texttt{Natasha}Natasha的算法本质上就是Prim\texttt{Prim}Prim算法它维护一个已访问集合每次从优先队列中取出dist最小的节点然后对于未访问的邻居如果边权小于当前记录的dist则更新dist为边权并记录前驱。注意这里的dist并不是从源点到该节点的累计距离而仅仅是连接边的最小权值因此它实际上是在构建一棵最小生成树从源点出发的Prim\texttt{Prim}Prim树。题目要求重新分配边权使用给定的权重集合{1,2,…,m}\{1,2,\dots,m\}{1,2,…,m}使得在这组新权重下Natasha\texttt{Natasha}Natasha算法最终输出的ans沿前驱累加边权恰好等于从源点到每个节点的真实最短路径同样在新权重下。也就是说我们需要构造一组边权排列使得Prim\texttt{Prim}Prim算法选出的边恰好构成一棵最短路径树SPT\texttt{SPT}SPT。解题思路关键观察Prim\texttt{Prim}Prim算法在选择边时总是选择当前已访问集合到未访问集合的最小权边。如果我们能够控制权重的分配使得Prim\texttt{Prim}Prim在扩展时严格按照某种层次顺序进行那么它就能生成一棵特定的树。最短路径树的一个自然候选是从源点出发的BFS\texttt{BFS}BFS树因为它保证了从源点到每个节点的跳数最少。但仅凭跳数少并不足以保证路径总权值最小我们需要进一步设计权值使得BFS\texttt{BFS}BFS树路径的总权值严格小于任何经过非树边的路径。构造方法我们可以利用BFS\texttt{BFS}BFS的顺序来分配权重具体步骤如下从源点sourcesourcesource开始进行BFS\texttt{BFS}BFS遍历整个图。在BFS\texttt{BFS}BFS过程中每当从当前节点uuu第一次访问到一条连接未访问节点vvv的边(u,v)(u,v)(u,v)时就给这条边分配当前最小的未使用权重从111开始递增。这样BFS\texttt{BFS}BFS先发现的边获得较小的权重后发现的边获得较大的权重。为什么这样构造是可行的BFS\texttt{BFS}BFS保证了节点按离源点的跳数深度递增的顺序被访问。因此连接深度ddd和d1d1d1的边会在连接深度d1d1d1和d2d2d2的边之前被分配权重。由于所有权重都是按发现顺序递增的Prim\texttt{Prim}Prim算法在从源点开始扩展时会优先选择这些被早期分配的边。实际上Prim\texttt{Prim}Prim的扩展顺序将完全与BFS\texttt{BFS}BFS的层次顺序一致最终生成的就是这棵BFS\texttt{BFS}BFS树。对于任意一个深度为ddd的节点从源点到它的BFS\texttt{BFS}BFS树路径恰好包含ddd条边且这些边的权重依次为1,2,…,d1,2,\dots,d1,2,…,d因为它们是BFS\texttt{BFS}BFS过程中最先被分配的ddd条边路径总权值为12⋯dd(d1)212\cdotsd \frac{d(d1)}{2}12⋯d2d(d1)​。任何包含非树边的路径其第一条非树边一定是在BFS\texttt{BFS}BFS中较晚被发现即权重较大的边它的权值至少为d1d1d1。而整条路径的总权值必然大于等于这条非树边的权值因此必然大于d(d1)2\frac{d(d1)}{2}2d(d1)​当d≥1d \ge 1d≥1时d(d1)2d\frac{d(d1)}{2} d2d(d1)​d且d1dd1 dd1d但更严格地d(d1)2\frac{d(d1)}{2}2d(d1)​对于d≥2d \ge 2d≥2已经大于d1d1d1对于d1d1d1树路径权值为111而非树边最小为222也满足。所以树路径是严格最短的。因此这种分配方案能够保证BFS\texttt{BFS}BFS树就是新权重下的最短路径树从而Natasha\texttt{Natasha}Natasha算法即Prim\texttt{Prim}Prim会选中这些树边并最终输出正确的最短距离。算法步骤读取n,m,sourcen, m, sourcen,m,source。使用邻接矩阵或邻接表存储图的结构。从sourcesourcesource出发进行BFS\texttt{BFS}BFS初始化一个队列将sourcesourcesource入队。维护一个全局权重计数器w1w 1w1。当队列非空时弹出队首节点uuu遍历所有与uuu相邻的节点vvv如果边(u,v)(u,v)(u,v)尚未被分配权重则将其权重设为www并将www加111同时将vvv入队并标记该边已分配。BFS\texttt{BFS}BFS结束后每条边都被赋予了一个唯一的权重111到mmm。按输入顺序输出每条边的端点及对应的新权重。复杂度分析BFS\texttt{BFS}BFS遍历所有节点和边时间复杂度为O(nm)O(n m)O(nm)。使用邻接矩阵n≤2500n \le 2500n≤2500时每次检查所有邻居需要O(n2)O(n^2)O(n2)但nnn较小可以接受。也可以使用邻接表优化到O(nm)O(n m)O(nm)。空间复杂度O(n2)O(n^2)O(n2)邻接矩阵或O(nm)O(n m)O(nm)邻接表。代码实现// Fiasco// UVa ID: 12717// Verdict: Accepted// Submission Date: 2026-06-24// UVa Run Time: 0.070s//// 版权所有C2026邱秋。metaphysis # yeah dot net#includebits/stdc.husingnamespacestd;constintMAXN2505;intn,m,source;intg[MAXN][MAXN];// 存储分配后的边权-1 表示无边intorigU[25005],origV[25005];// 保存输入顺序的端点boolvisited[MAXN][MAXN];// 标记边是否已在 BFS 中被分配voidbfs(){queueintq;intweight1;q.push(source);while(!q.empty()){intuq.front();q.pop();for(intv1;vn;v){if(g[u][v]!-1!visited[u][v]){// 边 (u,v) 第一次被发现分配权重g[u][v]g[v][u]weight;visited[u][v]visited[v][u]true;q.push(v);}}}}intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);intT;cinT;for(intcs1;csT;cs){cinnmsource;// 初始化邻接矩阵memset(g,-1,sizeof(g));memset(visited,false,sizeof(visited));for(inti0;im;i){intw;cinorigU[i]origV[i]w;// 仅标记存在边权重暂存为 0后续被覆盖g[origU[i]][origV[i]]g[origV[i]][origU[i]]0;}bfs();coutCase cs:\n;for(inti0;im;i)coutorigU[i] origV[i] g[origU[i]][origV[i]]\n;}return0;}总结本题的关键在于将Prim\texttt{Prim}Prim算法的行为引导到构造最短路径树的目标上。通过BFS\texttt{BFS}BFS顺序分配边权我们可以让Prim\texttt{Prim}Prim按照BFS\texttt{BFS}BFS的层次扩展并利用权重递增的特点保证BFS\texttt{BFS}BFS树路径的总权值小于任何包含非树边的路径。这种构造方法巧妙地将图论中的BFS\texttt{BFS}BFS与Prim\texttt{Prim}Prim算法联系起来避免了复杂的贪心证明是解决此类“重排边权使错误算法变正确”问题的经典技巧。核心要点利用BFS\texttt{BFS}BFS天然的分层特性控制边的优先级。权重递增使树路径的权值和与深度挂钩确保最短性。代码实现简洁时间复杂度低适合题目给定的数据范围。