题解/算法 {2699. 修改图中的边权}
题解
LINK: https://leetcode.cn/problems/modify-graph-edge-weights/
;
这个题很难…
-{
先看个错误的思路;
将所有残缺边都设置为1, 然后得到最短路径 将这个路径长度修改为TarVal
(比如, 设最短路长度为L
, 则将路径上的某一条残缺边 修改为TarVal - L + 1
, 这样这条路径长度就会是TarVal
);
所有残缺边都是1时 此时该图的最短路径 假设其为Path, 但一旦你修改他的某条边 (原来为1 现在变大了) 这个路径 虽然他的长度变成了TarVal
, 但此时他就不再是全局的最短路了;
比如S--(5)--a--(-1)--T, S--(-1)--b--(-1)--T, TarVal = 7
, 将所有残缺边的权值置为1, 此时最短路是S-b-T
那么你要将他的长度从2变成7, 但是此时S-a-T
是新的最短路 他的长度为6;
那你可能认为: 将非最短路径上的所有残缺边 都置为2e9
;
这还是错误的, 比如S--(5)--a, S--(-1)--a--(-1)--T, TarVal = 7
你的最短路S-a-T
将他设置为S--(6)--a--(1)--T
, 可是a的最短路 其实不是6 而是5;
-}
任何想要通过只遍历最短路径的算法, 都是错误的, 因为他是动态的, 你只要修改任何一条边的权值, 全局的最短路径 就改变了! 你必须得重新去计算最短路径 ;
灵感: (1: 所有边权都是正值 (包括你所设定的残缺边的边权)), (2: Dijkstra算法满足无后效性);
从S开始跑Dijkstra, 当cur第一次出队列时 他的答案就确定了 (这是在经典的静态图下), 那么 此时 我们虽然会修改边权 (动态图), 但是 我们所修改后的边权值 他也是正值;
比如a--(-1)--b
假设此时a第一次出队列 (即他的答案dist
是确定了的), 此时b有2种情况: {1: b的最短路已经确定, 2: b的最短路尚未确定}
.
对于1:的情况, 这条边设置成多少都可以, 因为答案(即S--T
的最短路) 一定不会是S...a--b...T
, 因为显然S...b...T
更优;
.
.
其实这种情况不会发生 (LINK: @LOC_0
), 因为在之前的b时 他会把这条边(双向的) 设置成一个>0
的值, 也就是在a时 这条边的边权已经确定了, 这种情况其实会continue
掉);
对于2:的情况 (这是重点), 此时你要将这条边 设置成一个w > 0
的边权, 那么对于此时所有已经出队列的点 其实增加一条边权为w的a-b
的边 这个操作 不会影响所有已经出队列的点的最短路值 (这是由Dijkstra的无后效性所决定的, 因为此时所有出队列的点 最短路的最大值 就是dist[a]
, 假如新加的这条边 会影响到之前的点的话 即S...a--b...p
显然这条路径长度至少为dist[a] + w + (>0)
);
那么关键是, 这条边 要设置成多少呢?
要设置成多少, 就完全取决于 一个前提, 即最终图的最短路 是包含当前w
边的 也就是最短路是S...a--b...T
时 (假如他不是最短路, 那么 你把w
设置成任意的>0
都可以, 反正他又不会是最短路 自然不影响答案 (答案是要保证最短路的长度为TarVal
);
因此, 此时的前提是: 这个S...a--b...T
路径 他是最终的最短路, 他的长度为dist[a] + w + ?
, 此时dist[a]
是确定了的, 关键是求?
;
.
令G1为将所有残缺边都置为1, dist2[x]
表示以T为起点 到x的最短路径, 这个?
就等于dist2[b]
(也就是如果b...T
里有残缺边 则都是1);
.
此时用反证法, 因为当前 我们已经预测性的 假设性的(假设b...T
里的残缺边都是1) 将这个路径 置为了TarVal
; 谈论这个?
非常重要, 我们详细分析下; LINK: (https://editor.csdn.net/md/?not_checkout=1&articleId=130836112)-(@LOC_0)
, 根据这个定理, 我们可以完全把b...T
给拆分开 单独讨论, 即我们此时将所有已经出队列的点(包含他的边)从图中全部删除, 单独讨论 由所有未出队列的点所组成的子图里 b...T
的最短路径, 令最短路所有的边是e1,e2,...
长度为dist2[b]
;
.
假如ei
都不是残缺边, 那么无论剩下的残缺边怎么赋值 b...T
的最短路 始终为dist2[b]
是个常数, 因此? = dist2[b]
;
.
否则存在残缺边, 反证 假如某个残缺边e1: b-x, e2: x->y
最终的边权不是1 比如是2, 我们知道 因为S...a--b...T
前提规定了他是最短路, 即dist[a] + w = dist[b], dist[b] + e1 = dist[x]
, 此时由前面的前提 可以推出dist[x] + 1 = y
, 但是 最终的结果是dist[x] + 2 = y
说明b->x
并不是x(距离S)的最短路 也就是dist[x] != dist[b] + e1
而是存在更短的路径 更新了dist[x] = dist[p] = ?
, 即全局最短路 一定不会是S...a--b--x--y...T
(因为存在S...p--x...T
更优), 这于假设的前提相违;
那么, 假如他a--b
这条边 不是最终的最短路, 那么 其实你设置成任何数都可以, 反正不会最短路;
存在边界情况: 即你计算出的w = TarVal - dist[a] - dist2[b]
会是<0
的情况, 此时说明 所有形如S...a-b...T
的路径 他的长度 永远都是>TarVal
即他一定不会是答案, 此时也随便设置个值就可以;
MARK: @LOC_0
, 还有一点, 即这条边设置为w, 因为他是无向边 也就是 其实你不仅把a->b
设置为w, 而且其实也把b->a
也设置为了w, 这点是硬性要求 因为题目里 是以单个的一条无向边为单位的 自然他的两个有向边的边权是同一的;
代码
namespace D2 {
static constexpr int PointCountMaximum_ = 102;
bool IsFirstMeet[ PointCountMaximum_];
using Type_distance_ = int; Type_distance_ Distance_start[ PointCountMaximum_];
//--
void Run( int _start){
#define IS_FIRST_TIME_ IsFirstMeet
#define DIST_ Distance_start
memset( IS_FIRST_TIME_, true, sizeof( IS_FIRST_TIME_));
memset( DIST_, 0x7F, sizeof( DIST_));
priority_queue< pair< Type_distance_, int>, vector< pair< Type_distance_, int> >, greater<> > heap;
while( false == heap.empty()) heap.pop();
DIST_[ _start] = 0;
heap.push( {DIST_[ _start], _start});
while( false == heap.empty()){
auto dist = heap.top().first;
auto cur = heap.top().second;
heap.pop();
if( false == IS_FIRST_TIME_[ cur]){
continue;
}
IS_FIRST_TIME_[ cur] = false;
for( int nex, edge = Graph::Head[ cur]; ~edge; edge = Graph::Next[ edge]){
nex = Graph::Vertex[ edge];
auto wth = Graph::Weight[ edge];
if( (*E)[ Graph::Index[ edge]][ 2] < 0){
// 相比经典Dijkstra, 就多了这个修改`wth`的操作;
wth = max( 1, TarVal - dist - D1::Distance_end[ nex]);
(*E)[ Graph::Index[ edge]][ 2] = wth;
}
if( dist + wth < DIST_[ nex]){
DIST_[ nex] = dist + wth;
heap.push( { DIST_[ nex], nex});
}
}
}
#undef IS_FIRST_TIME_
#undef DIST_
}
}
} // namespace Dijkstra
class Solution {
public:
vector<vector<int>> modifiedGraphEdges(int N, vector<vector<int>>& EE, int Start, int End, int TTarVal) {
Graph::initialize();
E = &EE;
TarVal = TTarVal;
for( int i = 0; i < (int)EE.size(); ++i){
// 所有残缺边置为1
auto a = EE[ i][ 0], b = EE[ i][ 1], w = EE[ i][ 2];
Graph::Add_edge( a, b, w > 0 ? w : 1, i);
Graph::Add_edge( b, a, w > 0 ? w : 1, i);
}
Dijkstra::D1::Run( End); // 以`End`为起点 预处理最短路
Dijkstra::D2::Run( Start); // 重点;
if( Dijkstra::D2::Distance_start[ End] != TarVal)
return {};
return EE;
}
};