题解/算法 {2699. 修改图中的边权}

题解/算法 {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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值