SRM626D1Med NegativeGraphDiv1

これは教育的

問題概要(原文)

$N$ 頂点 $M$ 本の重みつき有向辺からなるグラフがある. $0$ から $N-1$ への最短路を求めよ.ただし, $K$ 回までは,辺のコストを $-1$ 倍した値として扱うことができる.

考察

基本的には最短路問題なことはわかる. 辺のコストを $-1$ 倍した回数が $1,2,4,8, \ldots, 2^k$ 回以内のときの $s \rightarrow t$ 最短路を求めよう. これはワーシャルフロイド法を用いて $O(N^2 M + N^3logK)$ でできる. あとは, 繰り返し二乗法の要領で, $0 \rightarrow 1$ 最短路を求めればよい.

ソースコード

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Debug = System.Diagnostics.Debug;
using StringBuilder = System.Text.StringBuilder;
public class NegativeGraphDiv1
{
    public long findMin(int n, int[] from, int[] to, int[] weight, int charges)
    {
        var m = from.Length;
        const long INF = 1L << 60;
        var g = Enumerate(n, x => new long[n]);
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                g[i][j] = i != j ? INF : 0;

        for (int i = 0; i < m; i++)
        {
            from[i]--; to[i]--;
            g[from[i]][to[i]] = Math.Min(g[from[i]][to[i]], weight[i]);
            //Debug.WriteLine("{0} {1} {2}", from[i], to[i], weight[i]);
        }
        for (int k = 0; k < n; k++)
            for (int i = 0; i < n; i++)
                for (int j = 0; j < n; j++)
                    g[i][j] = Math.Min(g[i][j], g[i][k] + g[k][j]);


        var d = Enumerate(35, x => Enumerate(n, y => new long[n]));
        for (int k = 0; k < 35; k++)
            for (int i = 0; i < n; i++)
                for (int j = 0; j < n; j++)
                    d[k][i][j] = INF;
        for (int i = 0; i < m; i++)
        {
            for (int f = 0; f < n; f++)
                for (int t = 0; t < n; t++)
                    d[0][f][t] = Math.Min(d[0][f][t], Math.Min(g[f][t], g[f][from[i]] + g[to[i]][t] - weight[i]));
        }
        for (int k = 1; k < 35; k++)
        {
            for (int t = 0; t < n; t++)
                for (int i = 0; i < n; i++)
                    for (int j = 0; j < n; j++)
                        d[k][i][j] = Math.Min(d[k][i][j], d[k - 1][i][t] + d[k - 1][t][j]);

        }
        for (int t = 0; t < 35; t++, charges /= 2)
        {
            if ((charges & 1) == 0) continue;
            var next = Enumerate(n, x => new long[n]);
            for (int i = 0; i < n; i++)
                for (int j = 0; j < n; j++)
                    next[i][j] = INF;
            for (int k = 0; k < n; k++)
                for (int i = 0; i < n; i++)
                    for (int j = 0; j < n; j++)
                        next[i][j] = Math.Min(next[i][j], g[i][k] + d[t][k][j]);
            g = next;
        }

        return g[0][n - 1];
    }

    static public T[] Enumerate<T>(int n, Func<int, T> f) { var a = new T[n]; for (int i = 0; i < n; ++i) a[i] = f(i); return a; }

}

コメント

  • 繰り返し二乗法に気づけば簡単