Problem p00763 (C++) — buggy submission, verdict: Memory Limit Exceeded

// 考察
//?単純な場合を求める
// ・(今いる頂点i,
// 前使った鉄道の種類j,　種類jの鉄道を連続で使った距離k)が同じなら、合計運賃(コスト)は小さいほうがよい
// ・でもこれを状態とすると、状態数はO(n * c * 合計距離MAX ),
// 合計距離MAXは最悪1万は軽く超えるので、TLE&MLE不可避。
// ・経路中で用いられる鉄道の種類はたくさん＆複雑
// ・一つの鉄道を使ってある地点からある地点へ行く最適な経路自体は、頂点だけを状態とした最短経路問題を解けば求まる。
// ↑の場合、今までの合計距離 =
// 前に使った鉄道と同じ種類の鉄道を連続で用いた距離なので、状態が減る。
// ただし、このときのグラフは非連結になるかもしれないので、注意。どうせワーシャルフロイドするので問題ないけど。
// ちなみに、直接最小運賃を求めるのは難しいので一工夫する。
// 運賃は距離について単調増加だから、最短距離をワーシャルフロイドなどで求めてから、それを運賃に変換する。

//?実はこれが状態数増加の元凶！、元の問題を解く
// さて、元のグラフについて考えてみる。元のグラフ中の経路でも、所詮は、”一つの鉄道をある頂点からある頂点まで使う”を繰り返している。
// 前に使った種類の鉄道を続けて使う場合を考えず、今いるノードで”必ず鉄道の種類を切り替える”すなわち”ここで小計運賃を精//算する”としても、よいのでは？
// →ある地点からある地点まである鉄道だけを使って行くときの最小運賃が分かっていればできそう。(なんとなく)
// そうすれば、状態としては、やはり”頂点”しか持たなくてよくなる。鉄道を切り替えるので、合計距離や前の鉄道の種類が
// これからの合計運賃の増分に影響しないから。

// このようにすれば、?は全点対を各種鉄道について行うのでO(counter * n^3 + m)
//?は、各頂点での遷移数がO(counter * n)あるので、
// priority_queueダイクストラならO(counter * n^2log(counter *
// n^2))、queueダイクストラならO(counter * n^2)
// で求まる。(?をpriority_queueで実装しても)多分間に合うし、メモリは大丈夫だろう。ワーシャルフロイドは定数軽いし…

#include <cstdio>
#include <functional>
#include <iostream>
#include <queue>
#define int long long
using namespace std;

// 入力に関するもの。入力値の編集はこの中で・入力値はここから取得
class Input {
public:
  int n, m, counter, s, g;
  int from[10000], to[10000], dist[10000],
      type[10000]; // 全部0-indexedに変換する
  int segNum[20]; // segNum[i] = 鉄道会社i(>=0)の距離→運賃グラフの折れ線の数
  int seg[20][50]; // seg[][i]  = 折れ線i(>=0)の終点(区切り)
  int toler[20][50]; // toler[][i] = 折れ線i(>=0)の公差(iについて単調減少)

  int feeTable[20][22001]; // feeTable[i][j] =
                           // 鉄道会社iの鉄道を連続で距離jだけ使った時の料金
  int superDist[20]; // superDist[i] = seg[i][segNum[i]-2]
  int superFeeConst[20], superFeeToler[20]; // 鉄道会社iの鉄道を連続で距離j(>superDist[i])だけ使った時の料金
                                            // = 														superFeeConst[i]
                                            // + superFeeToler[i] * (j -
                                            // superDist[i])
  bool input() {
    scanf("%d%d%d%d%d", &n, &m, &counter, &s, &g);
    s--;
    g--;
    if (n == 0) {
      return false;
    }

    for (int i = 0; i < m; i++) {
      scanf("%d%d%d%d", from + i, to + i, dist + i, type + i);
      from[i]--;
      to[i]--;
      type[i]--;
    }
    for (int i = 0; i < counter; i++) {
      scanf("%d", segNum + i);
    }
    for (int i = 0; i < counter; i++) {
      for (int j = 0; j < segNum[i] - 1; j++) {
        scanf("%d", seg[i] + j);
      }
      for (int j = 0; j < segNum[i]; j++) {
        scanf("%d", toler[i] + j);
      }
    }

    for (int i = 0; i < counter; i++) {
      feeTable[i][0] = 0;
      for (int k = 1; k <= seg[i][0]; k++) {
        feeTable[i][k] = feeTable[i][k - 1] + toler[i][0];
      }
      for (int j = 1; j < segNum[i] - 1; j++) {
        for (int k = seg[i][j - 1] + 1; k <= seg[i][j]; k++) {
          feeTable[i][k] = feeTable[i][k - 1] + toler[i][j];
        }
      }
      superDist[i] = seg[i][segNum[i] - 2];
      superFeeConst[i] = feeTable[i][superDist[i]];
      superFeeToler[i] = toler[i][segNum[i] - 1];
    }
    return true;
  }
};

// 解くもの（入力そのまま使いたいから入力系を継承する)
class Solver : public Input {
public:
  int costTable
      [20][101]
      [101]; // costTable[i][j][k] =
             // 種類iの鉄道だけで頂点jから頂点kまで行くのにかかる合計運賃の最小値

  void initWorshal() {
    for (int i = 0; i < counter; i++) {
      for (int j = 0; j < n; j++) {
        for (int k = 0; k < n; k++) {
          costTable[i][j][k] = 1000000000;
          if (j == k) {
            costTable[i][j][k] = 0;
          }
        }
      }
    }
    for (int i = 0; i < m; i++) {
      costTable[type[i]][from[i]][to[i]] =
          min(dist[i], costTable[type[i]][from[i]][to[i]]);
      costTable[type[i]][to[i]][from[i]] =
          min(dist[i], costTable[type[i]][to[i]][from[i]]);
    }
  }
  void worshal() {
    for (int t = 0; t < counter; t++) {
      // 最短距離
      for (int k = 0; k < n; ++k)     // 中間ノード番号
        for (int i = 0; i < n; ++i)   // 始点ノード番号
          for (int j = 0; j < n; ++j) // 終点ノード番号
            costTable[t][i][j] = min(costTable[t][i][j],
                                     costTable[t][i][k] + costTable[t][k][j]);
      // 最小運賃への変換
      for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
          int diff = costTable[t][i][j] - superDist[t];
          if (diff < 0) {
            costTable[t][i][j] = feeTable[t][costTable[t][i][j]];
          } else {
            costTable[t][i][j] = superFeeConst[t] + diff * superFeeToler[t];
          }
        }
      }
    }
  }

  // priority_queueだと間に合わないらしい。
  typedef pair<int, int> P;
  queue<P> que;
  int Dijkstra(int st, int ed) {
    int mincost[101];
    for (int i = 0; i < n; i++) {
      mincost[i] = 1145141919;
    }
    que.push(P(0, st));

    while (!que.empty()) {
      P now = que.front();
      que.pop();
      int sc = now.first;
      int v = now.second;

      if (mincost[v] <= sc)
        continue;
      mincost[v] = sc;

      for (int i = 0; i < counter; i++) {
        for (int j = 0; j < n; j++) {
          // queue実装の場合は割と重要な枝刈り
          if (mincost[j] > sc + costTable[i][v][j]) {
            que.push(P(sc + costTable[i][v][j], j));
          }
        }
      }
    }
    return mincost[ed];
  }

  int solve() {
    initWorshal();
    worshal();

    // このデバッグをした後、0-indexedと1-indexedを間違えていただけのバグを見つけたなんて言えない。
    /*for (int i = 0; i < counter; i++) {
            for (int j = 1; j <= n; j++) {
                    for (int k = 1; k <= n; k++) {
                            if (costTable[i][j][k] >= 1000000000)
                                    printf("%4d", -1);
                            else
                                    printf("%4d", costTable[i][j][k]);
                    }
                    printf("\\n");
            }
            printf("\\n");
    }*/

    int res = Dijkstra(s, g);
    if (res >= 1000000000)
      return -1;
    return res;
  }
} solver;

signed main() {
  while (solver.input()) {
    int res = solver.solve();
    cout << res << endl;
  }
  return 0;
}

Error: MLE

Fixed submission:

// 考察
//?単純な場合を求める
// ・(今いる頂点i,
// 前使った鉄道の種類j,　種類jの鉄道を連続で使った距離k)が同じなら、合計運賃(コスト)は小さいほうがよい
// ・でもこれを状態とすると、状態数はO(n * c * 合計距離MAX ),
// 合計距離MAXは最悪1万は軽く超えるので、TLE&MLE不可避。
// ・経路中で用いられる鉄道の種類はたくさん＆複雑
// ・一つの鉄道を使ってある地点からある地点へ行く最適な経路自体は、頂点だけを状態とした最短経路問題を解けば求まる。
// ↑の場合、今までの合計距離 =
// 前に使った鉄道と同じ種類の鉄道を連続で用いた距離なので、状態が減る。
// ただし、このときのグラフは非連結になるかもしれないので、注意。どうせワーシャルフロイドするので問題ないけど。
// ちなみに、直接最小運賃を求めるのは難しいので一工夫する。
// 運賃は距離について単調増加だから、最短距離をワーシャルフロイドなどで求めてから、それを運賃に変換する。

//?実はこれが状態数増加の元凶！、元の問題を解く
// さて、元のグラフについて考えてみる。元のグラフ中の経路でも、所詮は、”一つの鉄道をある頂点からある頂点まで使う”を繰り返している。
// 前に使った種類の鉄道を続けて使う場合を考えず、今いるノードで”必ず鉄道の種類を切り替える”すなわち”ここで小計運賃を精//算する”としても、よいのでは？
// →ある地点からある地点まである鉄道だけを使って行くときの最小運賃が分かっていればできそう。(なんとなく)
// そうすれば、状態としては、やはり”頂点”しか持たなくてよくなる。鉄道を切り替えるので、合計距離や前の鉄道の種類が
// これからの合計運賃の増分に影響しないから。

// このようにすれば、?は全点対を各種鉄道について行うのでO(counter * n^3 + m)
//?は、各頂点での遷移数がO(counter * n)あるので、
// priority_queueダイクストラならO(counter * n^2log(counter *
// n^2))、queueダイクストラならO(counter * n^2)
// で求まる。(?をpriority_queueで実装しても)多分間に合うし、メモリは大丈夫だろう。ワーシャルフロイドは定数軽いし…

#include <cstdio>
#include <functional>
#include <iostream>
#include <queue>
#define int long long
using namespace std;

// 入力に関するもの。入力値の編集はこの中で・入力値はここから取得
class Input {
public:
  int n, m, counter, s, g;
  int from[10000], to[10000], dist[10000],
      type[10000]; // 全部0-indexedに変換する
  int segNum[20]; // segNum[i] = 鉄道会社i(>=0)の距離→運賃グラフの折れ線の数
  int seg[20][50]; // seg[][i]  = 折れ線i(>=0)の終点(区切り)
  int toler[20][50]; // toler[][i] = 折れ線i(>=0)の公差(iについて単調減少)

  int feeTable[20][22001]; // feeTable[i][j] =
                           // 鉄道会社iの鉄道を連続で距離jだけ使った時の料金
  int superDist[20]; // superDist[i] = seg[i][segNum[i]-2]
  int superFeeConst[20], superFeeToler[20]; // 鉄道会社iの鉄道を連続で距離j(>superDist[i])だけ使った時の料金
                                            // = 														superFeeConst[i]
                                            // + superFeeToler[i] * (j -
                                            // superDist[i])
  bool input() {
    scanf("%d%d%d%d%d", &n, &m, &counter, &s, &g);
    s--;
    g--;
    if (n == 0) {
      return false;
    }

    for (int i = 0; i < m; i++) {
      scanf("%d%d%d%d", from + i, to + i, dist + i, type + i);
      from[i]--;
      to[i]--;
      type[i]--;
    }
    for (int i = 0; i < counter; i++) {
      scanf("%d", segNum + i);
    }
    for (int i = 0; i < counter; i++) {
      for (int j = 0; j < segNum[i] - 1; j++) {
        scanf("%d", seg[i] + j);
      }
      for (int j = 0; j < segNum[i]; j++) {
        scanf("%d", toler[i] + j);
      }
    }

    for (int i = 0; i < counter; i++) {
      feeTable[i][0] = 0;
      for (int k = 1; k <= seg[i][0]; k++) {
        feeTable[i][k] = feeTable[i][k - 1] + toler[i][0];
      }
      for (int j = 1; j < segNum[i] - 1; j++) {
        for (int k = seg[i][j - 1] + 1; k <= seg[i][j]; k++) {
          feeTable[i][k] = feeTable[i][k - 1] + toler[i][j];
        }
      }
      superDist[i] = seg[i][segNum[i] - 2];
      superFeeConst[i] = feeTable[i][superDist[i]];
      superFeeToler[i] = toler[i][segNum[i] - 1];
    }
    return true;
  }
};

// 解くもの（入力そのまま使いたいから入力系を継承する)
class Solver : public Input {
public:
  int costTable
      [20][101]
      [101]; // costTable[i][j][k] =
             // 種類iの鉄道だけで頂点jから頂点kまで行くのにかかる合計運賃の最小値

  void initWorshal() {
    for (int i = 0; i < counter; i++) {
      for (int j = 0; j < n; j++) {
        for (int k = 0; k < n; k++) {
          costTable[i][j][k] = 1000000000;
          if (j == k) {
            costTable[i][j][k] = 0;
          }
        }
      }
    }
    for (int i = 0; i < m; i++) {
      costTable[type[i]][from[i]][to[i]] =
          min(dist[i], costTable[type[i]][from[i]][to[i]]);
      costTable[type[i]][to[i]][from[i]] =
          min(dist[i], costTable[type[i]][to[i]][from[i]]);
    }
  }
  void worshal() {
    for (int t = 0; t < counter; t++) {
      // 最短距離
      for (int k = 0; k < n; ++k)     // 中間ノード番号
        for (int i = 0; i < n; ++i)   // 始点ノード番号
          for (int j = 0; j < n; ++j) // 終点ノード番号
            costTable[t][i][j] = min(costTable[t][i][j],
                                     costTable[t][i][k] + costTable[t][k][j]);
      // 最小運賃への変換
      for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
          int diff = costTable[t][i][j] - superDist[t];
          if (diff < 0) {
            costTable[t][i][j] = feeTable[t][costTable[t][i][j]];
          } else {
            costTable[t][i][j] = superFeeConst[t] + diff * superFeeToler[t];
          }
        }
      }
    }
  }

  // priority_queueだと間に合わないらしい。
  typedef pair<int, int> P;
  queue<P> que;
  int Dijkstra(int st, int ed) {
    int mincost[101];
    for (int i = 0; i < n; i++) {
      mincost[i] = 1145141919;
    }
    que.push(P(0, st));

    while (!que.empty()) {
      P now = que.front();
      que.pop();
      int sc = now.first;
      int v = now.second;

      if (mincost[v] <= sc)
        continue;
      mincost[v] = sc;

      for (int i = 0; i < counter; i++) {
        for (int j = 0; j < n; j++) {
          // if (mincost[j] > sc + costTable[i][v][j] ) {
          que.push(P(sc + costTable[i][v][j], j));
          //}
        }
      }
    }
    return mincost[ed];
  }

  int solve() {
    initWorshal();
    worshal();

    // このデバッグをした後、0-indexedと1-indexedを間違えていただけのバグを見つけたなんて言えない。
    /*for (int i = 0; i < counter; i++) {
            for (int j = 1; j <= n; j++) {
                    for (int k = 1; k <= n; k++) {
                            if (costTable[i][j][k] >= 1000000000)
                                    printf("%4d", -1);
                            else
                                    printf("%4d", costTable[i][j][k]);
                    }
                    printf("\\n");
            }
            printf("\\n");
    }*/

    int res = Dijkstra(s, g);
    if (res >= 1000000000)
      return -1;
    return res;
  }
} solver;

signed main() {
  while (solver.input()) {
    int res = solver.solve();
    cout << res << endl;
  }
  return 0;
}