首页 > 编程语言 >时间复杂度O(40n*n)的C++算法:修改图中的边权

时间复杂度O(40n*n)的C++算法:修改图中的边权

时间:2023-10-24 16:01:53浏览次数:37  
标签:const vDis int 40n 复杂度 long vLess0Edge C++ iSize


1.12.1. 题目
给你一个 n 个节点的 无向带权连通 图,节点编号为 0 到 n - 1 ,再给你一个整数数组 edges ,其中 edges[i] = [ai, bi, wi] 表示节点 ai 和 bi 之间有一条边权为 wi 的边。
部分边的边权为 -1(wi = -1),其他边的边权都为 正 数(wi > 0)。
你需要将所有边权为 -1 的边都修改为范围 [1, 2 * 10^9] 中的 正整数 ,使得从节点 source 到节点 destination 的 最短距离 为整数 target 。如果有 多种 修改方案可以使 source 和 destination 之间的最短距离等于 target ,你可以返回任意一种方案。
如果存在使 source 到 destination 最短距离为 target 的方案,请你按任意顺序返回包含所有边的数组(包括未修改边权的边)。如果不存在这样的方案,请你返回一个 空数组 。
注意:你不能修改一开始边权为正数的边。
1 <= n <= 100
1 <= edges.length <= n * (n - 1) / 2
edges[i].length == 3
0 <= ai, bi < n
wi = -1 或者 1 <= wi <= 107
ai != bi
0 <= source, destination < n
source != destination
1 <= target <= 109
输入的图是连通图,且没有自环和重边。

分析

时间复杂度O(40n*n)的C++算法:修改图中的边权_最短路径


时间复杂度O(40n*n)的C++算法:修改图中的边权_最短路径_02

难点分析

任意边的权加1,则任意两点的最短路径要么不变,要么加1。前者对应至少有一条最短路径不经过此边;后者对应所有最短路径都经过此边。首先所有可变权的边,设置为1,每轮选择一条可以加权的权边加1。时间复杂度O(109*边数),时间复杂度太高,改成按顺序处理可变权边,就可以用二分法了,时间复杂度降为O(log(109*边数)约等于O(40)。

时间复杂度

每轮需要寻找最短路径,由于是稠密图,所以用朴素迪氏寻找单源路径。总时间复杂度是:O(log(10^9边数)nn),越O(40100*100)。

大致流程

如果可边权边设置为最大值,如果最短路径小于目标值,返回空。
如果可边权边设置为最小值,如果最短路径大于目标值,返回空。
二分寻找合适的值。

代码讲解

m_vMore0Edge,m_vLess0Edge分别记录不可变权边和可边权边。
CNeiBo3 是封装好的类,用与将权边转成邻接表。
CN2Dis 是封装好的求单源最短路径的类。
Do,求最短路径。
CalLess0Edge将增加的权分配给各边。

核心代码

//朴素迪杰斯特拉算法
class CN2Dis
 {
 public:
 CN2Dis(int iSize) :m_iSize(iSize), DIS(m_vDis), PRE(m_vPre)
 {}
 void Cal(int start, const vector<vector<pair<int, int>>>& vNeiB)
 {
 m_vDis.assign(m_iSize, -1);
 m_vPre.assign(m_iSize, -1);
 vector vDo(m_iSize);//点是否已处理
 auto AddNode = [&](int iNode)
 {
 //const int iPreNode = m_vPre[iNode];
 long long llPreDis = m_vDis[iNode];vDo[iNode] = true;
  for (const auto& it : vNeiB[iNode])
  {
    if (vDo[it.first])
    {
      continue;
    }

    if ((-1 == m_vDis[it.first]) || (it.second + llPreDis < m_vDis[it.first]))
    {
      m_vDis[it.first] = it.second + llPreDis;
      m_vPre[it.first] = iNode;
    }
  }

  long long llMinDis = LLONG_MAX;
  int iMinIndex = -1;
  for (int i = 0; i < m_vDis.size(); i++)
  {
    if (vDo[i])
    {
      continue;
    }
    if (-1 == m_vDis[i])
    {
      continue;
    }
    if (m_vDis[i] < llMinDis)
    {
      iMinIndex = i;
      llMinDis = m_vDis[i];
    }
  }
  return (LLONG_MAX == llMinDis) ? -1 : iMinIndex;
};

int next = start;
m_vDis[start] = 0;
while (-1 != (next = AddNode(next)));}
 void Cal(int start, const vector<vector>& mat)
 {
 m_vDis.assign(m_iSize, LLONG_MAX);
 m_vPre.assign(m_iSize, -1);
 vector vDo(m_iSize);//点是否已处理
 auto AddNode = [&](int iNode)
 {
 long long llPreDis = m_vDis[iNode];
 vDo[iNode] = true;
 for (int i = 0; i < m_iSize; i++)
 {
 if (vDo[i])
 {
 continue;
 }
 const long long llCurDis = mat[iNode][i];
 if (llCurDis <= 0)
 {
 continue;
 }
 m_vDis[i] = min(m_vDis[i], m_vDis[iNode] + llCurDis);
 }
 long long llMinDis = LLONG_MAX;
 int iMinIndex = -1;
 for (int i = 0; i < m_iSize; i++)
 {
 if (vDo[i])
 {
 continue;
 }
 if (m_vDis[i] < llMinDis)
 {
 iMinIndex = i;
 llMinDis = m_vDis[i];
 }
 }
 if (LLONG_MAX == llMinDis)
 {
 return -1;
 }m_vPre[iMinIndex] = iNode;
  return iMinIndex;
};

int next = start;
m_vDis[start] = 0;
while (-1 != (next = AddNode(next)));}
 const vector& DIS;
 const vector& PRE;
 private:
 const int m_iSize;
 vector m_vDis;//各点到起点的最短距离
 vector m_vPre;//最短路径的前一点
 };class CNeiBo3
 {
 public:
 CNeiBo3(int n, vector<vector>& edges, bool bDirect, int iBase = 0)
 {
 m_vNeiB.resize(n);
 AddEdges(edges, bDirect, iBase);
 }
 CNeiBo3(int n)
 {
 m_vNeiB.resize(n);
 }
 void AddEdges(vector<vector>& edges, bool bDirect, int iBase = 0)
 {
 for (const auto& v : edges)
 {
 m_vNeiB[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
 if (!bDirect)
 {
 m_vNeiB[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
 }
 }
 }
 vector<vector<std::pair<int,int>>> m_vNeiB;
 };
class Solution {
public:
  vector<vector<int>> modifiedGraphEdges(int n, vector<vector<int>>& edges, int source, int destination, int target) {
    m_n = n;
    m_src = source;
    m_dest = destination;
    for (const auto& v : edges)
    {
      if (-1 == v[2])
      {
        m_vLess0Edge.emplace_back(v);
      }
      else
      {
        m_vMore0Edge.emplace_back(v);
      }
    }
    long long left = 0, r = (long long)(1000 * 1000 * 1000-1)* m_vLess0Edge.size()+1;
    while (r - left > 1)
    {
      const auto mid = left + (r - left) / 2;
      const long long ll = Do(mid);
      if ( ll == target )
      {
        m_vLess0Edge.insert(m_vLess0Edge.end(), m_vMore0Edge.begin(), m_vMore0Edge.end());
        return m_vLess0Edge;
      }
      else if (ll > target)
      {
        r = mid;
      }
      else
      {
        left = mid;
      }
    }
    const long long ll = Do(left);
    if (target == ll)
    {
      m_vLess0Edge.insert(m_vLess0Edge.end(), m_vMore0Edge.begin(), m_vMore0Edge.end());
      return m_vLess0Edge;
    }
    return {};
  }
  long long Do(long long llAdd)
  {
    CalLess0Edge(llAdd);
    CNeiBo3 neiBo(m_n);
    neiBo.AddEdges(m_vMore0Edge,false);
    neiBo.AddEdges(m_vLess0Edge, false);
    CN2Dis dis(m_n);
    dis.Cal(m_src, neiBo.m_vNeiB);
    return dis.DIS[m_dest];
  }
  void CalLess0Edge(long long llAdd)
  {
    for (auto& v : m_vLess0Edge)
    {
      const int curAdd = (int)min(llAdd, (long long)1000 * 1000 * 1000 - 1);
      v[2] = 1 + curAdd;
      llAdd -= curAdd;
    }
  }
  int m_n;
  int m_src;
  int m_dest;
  vector<vector<int>> m_vMore0Edge,m_vLess0Edge;
};

测试用例

由于本文篇幅过长,请自行下载测试样例。

其它


测试环境

操作系统:win7 开发环境: VS2019 C++17

相关下载

如果你想观其大略,建设下载《闻缺陷则喜算法册》doc版

博主想队大家说的话

墨家名称的来源:有所得以墨记之。

闻缺陷则喜的来由:早发现,早修改问题,成本更低

程序是龙,算法是睛

时间复杂度O(40n*n)的C++算法:修改图中的边权_二分_03


标签:const,vDis,int,40n,复杂度,long,vLess0Edge,C++,iSize
From: https://blog.51cto.com/u_15724537/8005235

相关文章

  • 在C++中,互斥变量(std::mutex)是用于保护共享资源的重要工具,但它们确实有一些局限性,其中
    在C++中,互斥变量(std::mutex)是用于保护共享资源的重要工具,但它们确实有一些局限性,其中之一是无法保证包含指针的区域的多线程安全。这是因为互斥锁本质上只能保护它们所保护的代码块,而不会考虑指针指向的数据。下面是一些与互斥锁和指针相关的常见问题和注意事项:共享数据的复制:......
  • C++_Cmake的使用
    C++系统版本、软件依赖版本、组件LSB(全称:LinuxStandardsBase)LSBsharedobjectELF是ExecutableLinkableFormat的缩写,是Linux的链接、可执行、共享库的格式标准,COFF:CommonObjectCOFF(通用对象文件格式) 编译器:简单构建gcc编译流程分为4个步骤,分......
  • C++初识(续篇)
    1.2注释作用:在代码中加一些说明和解释,方便自己或其他程序员阅读代码两中格式单行注释:通常放在一行代码的上方,或者一条语句的末尾,对该行代码说明//这样的是单行注释多行注释:通常放在一段代码的上方,对该段代码做整体说明/*这种的是多行注释可以写好多行*/......
  • c++中的宏#define用途
    宏的一些作用,包括但不限于这些定义一个变量、字符串、类型定义一个函数、条件表达式条件编译、调试信息,异常类定义结构体、命名空间定义模版、枚举、函数对象#define宏定义在C++中用于定义常量、函数、条件编译、字符串、条件表达式、变量、注释、调试信息、类型、函数等,下面是一些......
  • C++的编译链接与在vs中build提速
    通过gcc或msvc,clang等编译器编译出来的C++源文件是.o文件。在windows上也就是PE文件,linux为ELF文件,在这一步中,调用其它代码文件中的函数的函数地址是未知的(00000),等到链接之后才会替换掉函数地址的linux,windows可执行文件(ELF、PE)C++是如何编译的C/C++编译过程主要分为4个过程......
  • C++常用语法知识--数据类型
    C++常用语法知识--数据类型C++为用户提供了7种基本C++数据类型:类型关键字字节大小布尔型bool1字符型char1有符号字符型signedchar1无符号字符型unsignedchar1整型int4有符号整型signedint4无符号整型unsignedint4短整型int2......
  • C++常用语法知识-- std::istringstream
    C++常用语法知识--std::istringstream介绍std::istringstream是C++标准库中的一个类,它用于从字符串中提取数据,并将数据转换为不同的数据类型。通常从字符串中解析数据,例如整数、浮点数等。使用方法创建std::istringstream对象,首先,需要创建一个std::istringstream对象,将......
  • C++常量
    上篇文章:C++中的变量https://blog.51cto.com/u_15965130/7979028常量常量,顾名思义,是常数的量,也就是说在定义的时候初始化一个值,这个值时固定的,在以后的程序运行中不可修改。常量修饰符constC++中常量的修饰符是const关键字。即,在变量的基础上添加const关键字,表示这个量是常量,......
  • C++常用知识语法--双冒号
    C++常用知识语法--双冒号作用域符号::的前面一般是类名称,后面一般是该类的成员名称,C++为避免不同的类有名称相同的成员而采用作用域的方式进行区分例如:A、B表示两个类,在A、B中都有成员member。A::member就表示类A中的成员memberB::member就表示类B中的成员member全局作用......
  • Python调用C或者C++
    基本说明文件类型介绍.out是可执行文件,相当于win上的exe;.o是编译中间目标文件,相当于win上的.obj;.a是静态库,多个.o练链接得到,用于静态链接;.so是共享库,用于动态链接,相当于win上.dll可执行文件file查看文件类型ldd命令查看某个可执行文件依赖了哪些动态链接库ldd能够显示......