読者です 読者をやめる 読者になる 読者になる

Codefores 374div2C Journey

Problem - C - Codeforces
まずはメモ化再帰

#include <bits/stdc++.h>
using namespace std;

using edge = pair<int, int>;
const int INF = 1e9 + 10;
vector<edge> adj[5001];
int memo[5001][5001];
// memo[i][j] := dist to node[0][0]
short mae[5001][5001];
int dfs(short cur_v, int used) {
    int& res = memo[cur_v][used];
    if(res != -1) return res;
    res = INF;
    if(used <= 0) return res;
    for(auto e : adj[cur_v]) {
        int pre_v, cost;
        tie(pre_v, cost) = e;
        int tmp = dfs(pre_v, used - 1);
        tmp += cost;
        if(res > tmp) {
            res = tmp;
            mae[cur_v][used] = pre_v;
        }
    }
    return res;
}

int main() {
    cin.tie(0); ios::sync_with_stdio(false);
    int N, M, T;
    cin >> N >> M >> T;
    for(int _ : _in(M)) {
        int u, v, c;
        cin >> u >> v >> c;
        --u; --v;
        adj[v].emplace_back(u, c);
    }

    memset(memo, -1, sizeof(memo));
    memset(mae, -1, sizeof(mae));
    memo[0][0] = 0;
    int ans = 0;
    for(int i : _in(N, 0)) {
        int res = dfs(N - 1, i);
        if(res != -1 && res <= T) {
            ans = i; break;
        }
    }
    short cur = N - 1, tmp = ans;
    vector<short> vec;
    while(cur != -1) {
        vec.emplace_back(cur);
        cur = mae[cur][tmp];
        --tmp;
    }
    reverse(vec.begin(), vec.end());
    int S = vec.size();
    cout << S << '\n';
    for(auto x : vec) cout << x + 1 << ' ';
    cout << '\n';
    return 0;
}

次はトポロジカルソートしてDP

#include <bits/stdc++.h>
using namespace std;

struct Edge{ int to, cost;};
bool topologicalSort(vector<vector<Edge>>& adj, vector<int>& ord) {
    int N = adj.size();
    vector<int> deg(N);//入次数
    for(int i = 0; i < N; ++i)
      for(auto e : adj[i])
        ++deg[e.to];
    ord.assign(N, -1);
    int t = 0;
    for(int v = 0; v < N; ++v) 
      if(deg[v] == 0)
        ord[t++] = v;

    for(int h = 0; h < t; ++h) {
        int v = ord[h];
        for(auto e : adj[v]) {
            --deg[e.to];
            if(deg[e.to] == 0)
              ord[t++] = e.to;
        }
    }
    return t == N;
}


const int INF = 1e9 + 10;;
int main() {
    int V, E, T;
    scanf("%d%d%d", &V, &E, &T);
    vector<vector<Edge>> adj(V);
    for(int i = 0; i < E; ++i) {
        int u, v, t;
        scanf("%d%d%d", &u, &v, &t);
        --u; --v;
        adj[u].push_back({v, t});
    }
    vector<int> ord;
    topologicalSort(adj, ord);
    vector<vector<int>> dp(V, vector<int>(V + 1, INF));
    vector<vector<short>> preV(V, vector<short>(V + 1, -1));
    dp[0][1] = 0;
    for(int i : ord) {
        for(int j = 0; j < V; ++j) {
            if(dp[i][j] == INF) continue;
            for(auto e : adj[i])
              if(dp[i][j] + e.cost <= T)
                if(dp[e.to][j + 1] > dp[i][j] + e.cost)
                  dp[e.to][j + 1] = dp[i][j] + e.cost, preV[e.to][j + 1] = i;
        }
    }

    int num = 0;
    for(int i = V; i >= 0; --i) {
        if(dp[V - 1][i] < INF) {
            num = i;
            break;
        }
    }
    vector<int> ans;
    int cur = V - 1;
    while(cur != -1) {
        ans.emplace_back(cur);
        cur = preV[cur][num--];
    }
    reverse(ans.begin(), ans.end());
    int S = ans.size();
    printf("%d\n", S);
    for(int x : ans)
      printf("%d ", x + 1);
    puts("");
    return 0;
}

 トポロジカルソートは入次数が少ない順に番号づけする実装が効率よさそう(もちろんオーダーはDFSやBFSでも同じだが)だったのでこれにした。ただ入次数0の辺がスタート以外にもあって、スタートの番号が0じゃないとちゃんと動かないかもしれない。ゴールについても同様。
 トポロジカルソートがなんなのかよくわかっていなかったのだけど、結局すべての辺(有向辺)が→向きになるように頂点を並べ替えているらしい。
 こうするとなぜDPできるのかというと。頂点番号をループでまわすと、頂点iにいるとき、iを通る全ての経路について、iより前に通過する頂点番号はiより小さい。なので配るDPがキッチリ動作してくれる、ということだと思う。あまり自身はないけど。
 わかってしまえばDFS解はかなりかったるいようにも思える。実際(半分くらいはオーバーフローだけど)かなりWA出たし……。
[追記]
この時は半順序関係など、集合論の知識がなかった。悲しいなあ。