有效边赋值顺序对效率的影响

P3376 【模板】网络最大流

言琢დ @ 2024-02-04 19:40:40

如题,我调整了为 go 数组赋值的语句的位置,但是结果貌似和我想的不太一样。

signed main(){
  n = init(), m = init(), S = init(), T = init();
  for (int i = 1; i <= m; ++i)
    add(init(), init(), init());
  int ans = 0;
  while (true) {
    memset(dis, -1, sizeof(dis));
    memset(go, 0, sizeof(go));
    std::queue <int> Q;
    Q.push(S); dis[S] = 0;
    while (!Q.empty()) {
      int u = Q.front(); Q.pop();
      go[u] = head[u]; // u 的第一条有效边,必须是 head[u],否则顺序会出错
      for (int i = head[u]; i; i = s[i].next) {
        int v = s[i].to, w1 = s[i].flow, w2 = s[i].lim;
        if (w1 >= w2) continue;
        if (dis[v] != -1) continue;
        dis[v] = dis[u] + 1; // 分层图距离计算
        Q.push(v);
      }
    }
    if (dis[T] == -1) break;
    ans += dfs(S, MAX_INT);
  }
  print(ans), putchar('\n');
}

上面这篇代码,在每次结点 u 出队列的时候直接为它用 head[u] 赋值,保证了正确性。

而我考虑到按照遍历顺序来说,满足 w_1\ge w_2dis_v\ne -1 的点一定不会被访问,所以为了之后的 dfs 过程提供便利,我将 go 数组赋值调整成在判定了两个条件之后,找到第一个 i 并进行赋值。

signed main(){
  n = init(), m = init(), S = init(), T = init();
  for (int i = 1; i <= m; ++i)
    add(init(), init(), init());
  int ans = 0;
  while (true) {
    memset(dis, -1, sizeof(dis));
    memset(go, 0, sizeof(go));
    std::queue <int> Q;
    Q.push(S); dis[S] = 0;
    while (!Q.empty()) {
      int u = Q.front(); Q.pop();
      for (int i = head[u]; i; i = s[i].next) {
        int v = s[i].to, w1 = s[i].flow, w2 = s[i].lim;
        if (w1 >= w2) continue;
        if (dis[v] != -1) continue;
        if (!go[u]) go[u] = i; // 按照遍历顺序,u 的第一条有效边
        dis[v] = dis[u] + 1; // 分层图距离计算
        Q.push(v);
      }
    }
    if (dis[T] == -1) break;
    ans += dfs(S, MAX_INT);
  }
  print(ans), putchar('\n');
}

按照我的期望来说,应该是两篇代码均为正确,并且第二篇较快。

实际情况是两篇代码均为正确,并且第一篇较快。

第一篇用时 65ms,第二篇用时 334ms。

感觉第二篇进行的运算次数应该严格小于第一篇啊(毕竟跳过了一些完全无效的边)

为啥会反而慢这么多?


by 言琢დ @ 2024-02-04 19:42:15

Dinic 算法中 dfs 过程如下,为了防止占用页面太长,完整代码就不贴了,应该不影响阅读吧。(用于对比的两篇代码其他部分都相同,只有 go 数组赋值的那一处不同

int dfs(int u, int now){
  // now 表示从 s -> u 一路过来的合法流量最小值
  if (u == T) return now;
  int ans = 0; // ans 表示已经流出去的合法流量
  for (int i = go[u]; i; i = s[i].next) {
    if (ans >= now) break; // 从 u 开始已经流不出去流量
    go[u] = i;
    // 当前弧优化:每次总访问一条可能有效的边
    // 由于结点 u 可能被不同的前驱结点访问,所以别的流量再流进来时,编号为 i 的边之前的边一定已经流光了
    int v = s[i].to, w1 = s[i].flow, w2 = s[i].lim;
    if (dis[v] != dis[u] + 1) continue;
    // 分层图优化
    if (w1 >= w2) continue;
    // 还有流量剩余
    int cnt = dfs(v, mn(now - ans, w2 - w1));
    // 从 v 流出去的流量,必须满足 <= now-ans 且 <= w2-w1
    // 其中 ans 表示已经流出去,不复存在的流量,因此需要从 now 中去除
    if (!cnt) {
      dis[v] = MAX_INT; continue;
    }
    // 若从 v 已经流不出去流量,说明从 v 出去已经达到饱和
    s[i].flow += cnt;
    s[i ^ 1].flow -= cnt; // 流量修改
    ans += cnt; // 统计已经流出去的流量
  }
  return ans;
}

|