解いた問題のソースコードと解説など。


POJ 3422 Kaka's Matrix Travels

問題

NxNのグリッドがあり、各マスに0以上の整数が書かれている。グリッドの左上のマスからスタートして右か下に一つずつ移動して右下のマスまでたどり着きたい。通ったマスの数字は得点となり、一度通ったらそのマスの数字は0になる。K回スタートからゴールに移動したときに得られる総得点を最大化せよ。

やりかた

最小(最大)費用流。単純に最大経路をK回調べるのでは駄目。
費用流の問題として考えるとソースから左上のマスにつながり、右下のマスからシンクにつながるネットワークにKだけ流すときの最大費用流として考えることができる。ここで問題になるのは各マスが一回通ったあとは得点が0になることであり、ネットワークフローで考えればこれは(得点があるときの)そのセルに流量1の制限があることと同じになる。そこで下の図のように各マスのコピーを作り、コピーに対して1の流量のある得点がつく辺と、流量無限大のコスト0の辺を加えてやることで、得点のある辺は一回しか使われないようにすることができる。
f:id:Area1:20171218005854p:plain

以下ソース。

int N, K;
struct min_cost_flow{
  struct edge{
    int to, cap, cost, rev;
    edge(int to, int cap, int cost, int rev) : 
      to(to), cap(cap), cost(cost), rev(rev) {};
  };
  vector<vector<edge> > G;

  min_cost_flow(int V) : G(V) {}

  void add_edge(int from, int to, int cap, int cost){
    G[from].push_back(edge(to, cap, cost, G[to].size()));
    G[to].push_back(edge(from, 0, -cost, G[from].size() - 1));
  }

  int flow(int s, int t, int f){
    typedef pair<int, int> P;
    int V = G.size();
    vector<int> po(V);
    int res = 0;
    while(f > 0){
      vector<int> dist(V, INF), prevv(V, -1), preve(V, -1);
      priority_queue<P, vector<P>, greater<P> > Q;
      Q.push(make_pair(0, s));
      dist[s] = 0;
      while(!Q.empty()){
	P q = Q.top(); Q.pop();
	int u = q.second;
	if(dist[u] < q.first) continue;
	for(int i = 0; i < G[u].size(); i++){
	  edge &e = G[u][i];
	  if(e.cap > 0 && dist[e.to] > dist[u] + e.cost + po[u] - po[e.to]){
	    dist[e.to] = dist[u] + e.cost + po[u] - po[e.to];
	    prevv[e.to] = u;   preve[e.to] = i;
	    Q.push(P(dist[e.to], e.to));
	  }
	}
      }
      if(dist[t] == INF) return -1;
      for(int v = 0; v < V; v++) po[v] += dist[v];
      
      int scap = f;
      for(int v = t; v != s; v = prevv[v])
	scap = min(scap, G[prevv[v]][preve[v]].cap);
      
      f -= scap;
      res += scap * po[t];
      for(int v = t; v != s; v = prevv[v]){
	edge &e = G[prevv[v]][preve[v]];
	e.cap -= scap;
	G[v][e.rev].cap += scap;
      }
    }
    return res;
  }
};

int main(){
  scanf("%d %d", &N, &K);

  int V = N * N;
  int s = 2 * V, t = s + 1;
  
  min_cost_flow mcf(2 * V + 2);
  for(int i = 0; i < N; i++){
    for(int j = 0; j < N; j++){
      int x;
      scanf("%d", &x);

      mcf.add_edge(i * N + j, i * N + j + V, 1, -x);//最小費用流でとくため符号を反転
      mcf.add_edge(i * N + j, i * N + j + V, INF, 0);
      
      if(j != N - 1)
	mcf.add_edge(i * N + j + V, i * N + j + 1, INF, 0);
      if(i != N - 1)
      	mcf.add_edge(i * N + j + V, (i + 1) * N + j, INF, 0);
    }
  }
  mcf.add_edge(s, 0, INF, 0);
  mcf.add_edge(2 * V - 1, t, INF, 0);
  
  printf("%d\n", -mcf.flow(s, t, K));////最小費用流でとくため符号を反転
  return 0;
}

図が薄くてすみません。

Get up! 明日のSUPER ST@R!