NOSSの雑記

主に競プロでやったことを書きます。

Codeforces Round #304 (Div. 2) E. Soldier and Traveling

問題

 n 頂点  m 辺の無向が与えられる。最初、 i 番目の頂点には  a_i 人の兵士がおり、各兵士は隣接する頂点に移動するかその場にとどまるかを行う。ただし、兵士が移動できる辺の本数は1本までとする。

すべての移動後、 i 番目の頂点にいる兵士の人数を  b_i となるようにできるか判定せよ。可能である場合、その移動の方法を出力せよ。

問題リンク:Codeforces Round #304 (Div. 2) E. Soldier and Traveling

制約

  •  1 \le n \le 100
  •  0 \le m \le 200
  •  0 \le a_i, b_i \le 100

解法

 \sum a_i \neq \sum b_i では不可能なので  \sum a_i = \sum b_i とします。

問題を次のように読み替えます。「 i 番目の頂点への入力を a_i、出力を  b_i となるようにネットワークを構築できるか?」このようにするとフローによって問題を解くことができます。

辺を1度までしか使えないという制約のため、頂点を入力側と出力側に分けます。入力側から出力側へ隣接する頂点間と自分自身に辺を張ることで辺を1度までしか使用しないようにします。辺の容量は十分大きな値とします。

始点と終点を用意し、始点から入力点へ容量  a_i の辺、出力点から終点へ  b_i の辺を張ります。このとき始点から終点への最大流  F F = \sum a_i となっていれば構築可能です。

頂点間の流量は逆辺の容量からわかります。

Code

#include <bits/stdc++.h>
using namespace std;
 
constexpr int IINF = INT_MAX;
 
struct edge{
    int to, cap, rev;
    bool isrev;
};
 
vector<vector<edge> > G;
vector<bool> used;
 
void add_edge(int from, int to, int cap){
    G[from].emplace_back((edge){to, cap, int(G[to].size()), false});
    G[to].emplace_back((edge){from, 0, int(G[from].size())-1, true});
}
 
// 増加路の探索
int dfs(int v, int t, int f){
    if(v == t) return f;
    used[v] = true;
    for(auto &e : G[v]){
        if(!used[e.to] && e.cap > 0){
            int d = dfs(e.to, t, min(f, e.cap));
            if(d > 0){
                e.cap -= d;
                G[e.to][e.rev].cap += d;
                return d;
            }
        }
    }
    return 0;
}

// 最大流
int max_flow(int s, int t){
    used.resize(G.size());
    int flow = 0;
    while(1){
        fill(used.begin(), used.end(), false);
        int f = dfs(s, t, IINF);
        if(f==0) break;
        flow += f;
    }
    return flow;
}
 
int main() {
    int n, m, asum = 0, bsum = 0;
    cin >> n >> m;
    G.resize(n*2+2);  // 入力点、出力点、始点、終点
    for(int i=0;i<n;i++){
        int a;
        cin >> a;
        add_edge(n*2, i, a);  // 始点->入力点
        asum += a;
    }
    for(int i=0;i<n;i++){
        int b;
        cin >> b;
        add_edge(i+n, n*2+1, b);  // 出力点->終点
        bsum += b;
    }
    for(int i=0;i<n;i++){
        add_edge(i, i+n, IINF);  // とどまる辺
    }
    for(int i=0;i<m;i++){
        int p, q;
        cin >> p >> q;
        p--; q--;
        add_edge(p, q+n, IINF);  // 隣接する頂点間の辺
        add_edge(q, p+n, IINF);
    }
    if(asum != bsum || max_flow(n*2, n*2+1) != asum){
        cout << "NO" << endl;
    }
    else{
        cout << "YES" << endl;
        vector<vector<int> > ans(n, vector<int>(n,0));
        for(int i=0;i<n;i++){
            for(auto &e : G[i+n]){
                if(e.isrev){
                    ans[e.to][i] += e.cap;
                }
            }
        }
        // 出力
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                cout << ans[i][j] << " \n"[j==n-1];
            }
        }
    }
    return 0;
}