全方位木DPライブラリを作った

概要

森に対して全方位木DPを行うライブラリです。
全方位森DPの方がいいんかな。
森でないときにassertが落ちるようにするためにUnionFindを使っています。
パフォーマンスが気になるなら使わないように実装しなおすと良いかもしれません。

計算量

  • construct: O(n)
  • add: O(1)
  • dp: O(n\alpha(n)) (実質  O(n))

コード

using ll = long long;

class UnionFind {
  vector<ll> par, h;
public:
  UnionFind(ll size) : par(size, 0), h(size, 0) {
    for (int i = 0; i < size; ++i) {
      par[i] = i;
    }
  }
  void unite(ll u, ll v) {
    u = root(u), v = root(v);
    if (u == v) return;
    if (h[u] < h[v]) {
      par[u] = v;
    }
    else {
      par[v] = u;
    }
    if (h[u] == h[v]) ++h[u];
  }
  bool isUnited(ll u, ll v) {
    return root(u) == root(v);
  }
  ll root(ll v) {
    if (par[v] == v) return v;
    return par[v] = root(par[v]);
  }
};

template<class Calc>
class EdgeDpOnForest {
public:
  using T = typename Calc::type;
  struct Edge {
    ll to, rev;
    T value;
  };
private:
  ll n;
  void dfs1(ll v, ll pv) {
    ll idx = -1;
    vector<T> values;
    for (int i = 0; i < G[v].size(); ++i) {
      const Edge& e = G[v][i];
      if (e.to == pv) {
        idx = i;
        continue;
      }
      dfs1(e.to, v);
      values.push_back(e.value);
    }
    // 根以外では親から入る辺の値を更新
    if (idx >= 0) {
      const Edge& e = G[v][idx];
      G[e.to][e.rev].value = Calc::merge(values);
    }
  }
  void dfs2(ll v, ll pv) {
    vector<T> values;
    for (auto&& e : G[v]) values.push_back(e.value);
    values = Calc::evaluate(values);
    for (int i = 0; i < G[v].size(); ++i) {
      const Edge& e = G[v][i];
      if (e.to == pv) continue;
      G[e.to][e.rev].value = values[i];
      dfs2(e.to, v);
    }
  }
public:
  UnionFind uf;
  vector<vector<Edge>> G;
  EdgeDpOnForest(ll n) : n(n), uf(n), G(n) {}
  // 辺を追加する
  void add(ll u, ll v) {
    assert(!uf.isUnited(u, v));
    G[u].push_back({v, ll(G[v].size())});
    G[v].push_back({u, ll(G[u].size())-1});
    uf.unite(u, v);
  }
  // 各有向辺の値 G[v][i].value を計算する
  void dp() {
    vector<bool> used(n, false);
    for (int i = 0; i < n; ++i) {
      if (used[uf.root(i)]) continue;
      dfs1(i, -1);
      used[uf.root(i)] = true;
    }
    used.assign(n, false);
    for (int i = 0; i < n; ++i) {
      if (used[uf.root(i)]) continue;
      dfs2(i, -1);
      used[uf.root(i)] = true;
    }
  }
  size_t size() const {
    return G.size();
  }
};

// これらを実装してください
/*
struct Merger {
  using type = ll;
  // その頂点から出ていく値から入ってくる値を計算する
  static vector<type> evaluate(const vector<type>& value) {
  }
  // (辺数-1)個の出ていく値から1個の入ってくる値を計算する
  static type merge(const vector<type>& value) {
  }
};
*/

使用例 (問題: V - Subtree)

#include <bits/stdc++.h>

// ここに↑を貼る

ll M;

struct Merger {
  using type = ll;
  // その頂点から出ていく値から入ってくる値を計算する
  static vector<type> evaluate(const vector<type>& value) {
    const ll n = value.size();
    vector<type> L(n+1, 1), R(n+1, 1);
    for (int i = 0; i < n; ++i) {
      L[i+1] = (L[i] * value[i]) % M;
    }
    for (int i = n-1; i >= 0; --i) {
      R[i] = (R[i+1] * value[i]) % M;
    }
    vector<type> res(n);
    for (int i = 0; i < n; ++i) {
      res[i] = (L[i] * R[i+1] + 1) % M;
    }
    return res;
  }
  // (辺数-1)個の出ていく値から1個の入ってくる値を計算する
  static type merge(const vector<type>& value) {
    ll res = 1;
    for (auto&& x : value) {
      res = (res * x) % M;
    }
    res = (res + 1) % M;
    return res;
  }
};

int main() {
  ll n; cin >> n >> M;
  EdgeDpOnForest<Merger> dp(n);
  for (int i = 0; i < n-1; ++i) {
    ll a, b; cin >> a >> b; --a, --b;
    dp.add(a, b);
  }
  dp.dp();
  for (int i = 0; i < n; ++i) {
    ll ans = 1;
    for (auto&& e : dp.G[i]) {
      ans = (ans * e.value) % M;
    }
    cout << ans << endl;
  }
}

gdbでredirectが使えない

僕の環境

$ gdb a.out -eval-command run < nyuryoku

通常の環境

普通、gdbでredirectするには、下のようにすれば良いらしい。

$ gcc -g a.cpp -o a.out
$ gdb a.out
(gdb) run < nyuryoku

ただ、僕の環境では

(gdb) run "<" "nyuryoku"

と解釈されてしまいうまく動かなかった。
ggっても解決には至らなかったのでここに残そうと思う。

$ gdb a.out -eval-command run < nyuryoku

JISキーボードをUSキーボードとして使う

結論

Windows

Keymapする。

1. お手軽な方法

↓をDLして実行、気に入ったらスタートアップ(shell:startup)に登録する。

www.dropbox.com

2. 編集したいなら

AutoHotkey をインストールして以下のスクリプトhoge.ahk などと保存して実行する。

気に入ったらスタートアップ(shell:startup)に登録する。

※↓のkeymapは ~全角/半角 ではなく バックスペース の左隣のキーに割り当てている

#InstallKeybdHook
$+2::Send,{@}
$+6::Send,{^}
$+7::Send,{&}
$+8::Send,{*}
$+9::Send,{(}
$+0::Send,{)}
$^::Send,{=}
$~::Send,{+}
$+-::Send,{_}
$]::\
$+]::\
$+;::Send,{vkBAsc028}
$@::[
$[::Send,{]}
$+@::Send,{{}
$+[::Send,{}}
$\::`
$+\::Send,{~}
$vkBA::Send,{'}
$+vkBA::2
$+vkBB::Send,{vkBA}
$sc056::`
$+sc056::Send,{~}
Mac

Karabiner - Software for macOS を使ってkeymapする。

(詳しいサイトが多いのでggってください)

動機: USキーボードじゃだめなんです

USキーボードはキーが少なく、全角/半角切り替えのコストがどうしても大きくなる。

しかも今の入力モードを意識しないといけないToggleはヤダ。

無変換 キーを無変換の機能を残しながら(macの) 英数 キーに、
カタカナ, ひらがな キーをそのまま使いたい。

無変換 キーの設定をしたい人は↓が参考になるかも。
ただこのサイトでは無変換機能を殺しているので残すなら「変換前入力中」だけ元のままに。

freelifetech.com

python3でバイナリデータを標準出力に出力する

python3でcgiを書いていて、画像データを標準出力に出力しようとしてハマったのでメモ

結論

1. sys.stdout.buffer.write を使う
2. 先に print を使っていたなら sys.stdout.buffer.write の直前に sys.stdout.flush を呼ぶ

サンプルコード

print("Content-Type: image/png")
print("Content-Disposition: attachment; filename=hoge.png")
print("")
sys.stdout.flush() # printとwriteの間に呼ぶ
sys.stdout.buffer.write(data)

ECNA 2016 - D: Lost in Translation ★★☆☆☆

評価・感想

制約小さくて想定解とは異なる気もするけど、
DAGなら最小有向全域木の1ステップ目で終わるというのが面白かった。

問題

Englishで書かれた本があり、それをnヶ国語に翻訳したい。
m人の翻訳者の候補が居て、i番目の人は言語l_{i1}を言語l_{i2}(またはその逆)にコストc_iで行ってくれる。
Englishから各言語への翻訳ステップを最小化した上で、合計コストの最小値を出力せよ。
codeforces.com

制約

  • n \le 100
  • m \le 4500

入力例

4 6
Pashto French Amheric Swedish
English Pashto 1
English French 1
English Amheric 5
Pashto Amheric 1
Amheric Swedish 5
French Swedish 1

出力例

8

考察・解答

bfs順にしか辿れないようなグラフG'上で最小全域有向木をする。
ただしG'はDAGなので、最小全域有向木の0.5ステップくらいで終わる(sccしない)。
つまり、G'上のEnglish以外の各頂点について、
そこに入る辺の中からコスト最小の辺を1つずつ選んだグラフは
閉路を持たないから解である。

プログラム

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll, ll> P;

#define EACH(i,a) for (auto&& i : a)
#define FOR(i,a,b) for (ll i=(a);i<(b);++i)
#define RFOR(i,a,b) for (ll i=(b)-1;i>=(a);--i)
#define REP(i,n) FOR(i,0,n)
#define RREP(i,n) RFOR(i,0,n)
#define __GET_MACRO3(_1,_2,_3,NAME,...) NAME
#define rep(...) __GET_MACRO3(__VA_ARGS__,FOR,REP)(__VA_ARGS__)
#define rrep(...) __GET_MACRO3(__VA_ARGS__,RFOR,RREP)(__VA_ARGS__)
#define pb push_back
#define ALL(a) (a).begin(),(a).end()
#define chmin(x,v) x = min(x,v)
#define chmax(x,v) x = max(x,v)

const ll linf = 1e18;
const int inf = 1e9;
const double eps = 1e-12;
const double pi = acos(-1);

template<typename T>
istream& operator>>(istream& is, vector<T>& vec) {
    EACH(x,vec) is >> x;
    return is;
}
template<typename T>
ostream& operator<<(ostream& os, const vector<T>& vec) {
    rep(i,vec.size()) {
        if (i) os << " ";
        os << vec[i];
    }
    return os;
}
struct Edge {
    ll to, cost;
};
vector<ll> bfs(const vector<vector<Edge>>& G, const ll s) {
    const ll n = G.size();
    vector<ll> res(n, -1); res[s] = 0;
    queue<ll> Q; Q.push(s);
    while ( !Q.empty() ) {
        ll v = Q.front(); Q.pop();
        EACH(e, G[v]) {
            if (res[e.to] < 0) {
                res[e.to] = res[v]+1;
                Q.push(e.to);
            }
        }
    }
    return res;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll n, m; cin >> n >> m;
    vector<string> name(n); cin >> name;
    name.insert(name.begin(), "English"); ++n;
    map<string, ll> tbl;
    rep(i, n) {
        tbl[name[i]] = i;
    }
    vector<vector<Edge>> G(n);
    rep(i, m) {
        string a, b; ll c; cin >> a >> b >> c;
        ll from = tbl[a];
        ll to = tbl[b];
        G[from].pb({to, c});
        G[to].pb({from, c});
    }
    vector<ll> dist = bfs(G, 0);
    try {
        rep(i, n) {
            if (dist[i] < 0) throw 1;
        }
        ll ans = 0;
        rep(i, 1, n) {
            ll c = linf;
            EACH(e, G[i]) {
                if (dist[e.to]+1 == dist[i]) {
                    c = min(c, e.cost);
                }
            }
            assert(c < linf);
            ans += c;
        }
        cout << ans << endl;
    }
    catch (int err) {
        cout << "Impossible" << endl;
    }
}

2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) - G: Maximum Islands ★★★☆☆

評価・感想

フローと塗りつぶしの2ステップがあるけどそこまで実装重くなく, 教育的良問だと思った.
二部グラフの最大安定集合は二部グラフの最小点カバーになって最小カットになって最大流になってすごく楽しい.

状態は2つ?最小カット?でも最大化したい?は?双対??
と混乱していたがトイレに行って整理したら自明だった.
落ち着いて整理して問題解こうな.
そして最大流のライブラリがバグっててTLEった.
DAGはメモ化しようとここ数ヶ月で114514回言ってる.

問題

H×Wのグリッドがあり, 各マスはL(陸地), W(海), C(不明)である.
CをLかWに割り当てて島の数を最大化したい.
陸地は4近傍で隣接し, その連結成分を島とする.
codeforces.com

制約

  • W, H \le 40

入力例

5 4
LLWL
CCCC
CCCC
CCCC
LWLL

出力例

8

考察

  • すでにわかっているLの周り(4近傍)のCはWが最適
  • 答え = そのときの島の数 + 残りのCをいい感じに割り当てたときにできた島の数
  • 市松模様で良さそうに見えるけど実はダメ
WCWWCW
CCCCCC
WCWWCW
  • 残りのCから生まれたどの島も広さ1が最適
  • つまり, 次の問題になった

残りのCは4近傍で辺を張ると, 二部グラフで, 隣り合わないようにいくつかの頂点を選び, 選んだ頂点数を最大化したい

  • これは最大安定集合問題
  • 二部グラフなのでフローで解ける
  • DinicでO(|E|\sqrt{|V|})なので40^3となり間に合う

解答

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> P;

const ll linf = 1e18;

#define FOR(i,a,b) for(ll i = (a); i < (b); ++i)
#define RFOR(i,a,b) for(ll i = (b)-1; i >= (a); --i)
#define REP(i,n) FOR(i,0,n)
#define RREP(i,n) RFOR(i,0,n)
#define EACH(i,v) for(auto&& i : v)
#define ALL(v) (v).begin(),(v).end()
#define pb push_back

template<class T>
istream& operator>>(istream& is, vector<T>& v) {
    EACH(i, v) is >> i;
    return is;
}
template<class T>
ostream& operator<<(ostream& os, const vector<T>& v) {
    EACH(i, v) {
        if (i) os << " ";
        os << i;
    }
    return os;
}

class MaxFlow {
public:
    struct Edge {
        ll to, cap, rev;
    };
private:
    ll V;
    vector<vector<Edge>> G;
    vector<bool> used;
    vector<ll> bfs(ll s) {
        vector<ll> dist(V, linf);
        dist[s] = 0;
        queue<ll> Q; Q.push(s);
        while ( !Q.empty() ) {
            ll v = Q.front(); Q.pop();
            EACH(e, G[v]) {
                if (e.cap > 0 && dist[e.to] == linf) {
                    dist[e.to] = dist[v]+1;
                    Q.push(e.to);
                }
            }
        }
        return dist;
    }
    ll dfs(ll v, ll t, ll f, const vector<ll>& dist) {
        if (v == t) return f;
        if (used[v]) return 0;
        used[v] = true;
        EACH(e, G[v]) {
            if (e.cap > 0 && dist[e.to] == dist[v]+1) {
                ll d = dfs(e.to, t, min(f, e.cap), dist);
                if (d > 0) {
                    e.cap -= d;
                    G[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        return 0;
    }
public:
    const vector<vector<Edge>> Graph() {
        return G;
    }
    MaxFlow(ll V) : V(V), G(V) {}
    void init(ll n) {
        V = n;
        G.assign(V, vector<Edge>());
    }
    void add(ll from, ll to, ll cap) {
        assert(V > 0);
        G[from].pb({to, cap, (ll)G[to].size()});
        G[to].pb({from, 0, (ll)G[from].size()-1});
    }
    ll flow(ll s, ll t, ll f=linf) {
        ll res = 0;
        while (f > 0) {
            // cout << "BFS Start" << endl;
            vector<ll> dist = bfs(s);
            // cout << "BFS End" << endl;
            if (dist[t] == linf) break;
            while (f > 0) {
                // cout << "DFS Start" << endl;
                used.assign(V, false);
                ll df = dfs(s, t, f, dist);
                // cout << "DFS End" << endl;
                if (df == 0) break;
                f -= df;
                res += df;
                // cout << res << endl;
            }
        }
        return res;
    }
};

const ll dx[4] = {0, 1, 0, -1};
const ll dy[4] = {-1, 0, 1, 0};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll h, w; cin >> h >> w;
    auto inRange = [&](ll x, ll y) {
        return 0 <= x && x < w && 0 <= y && y < h;
    };
    vector<string> m(h); cin >> m;
    REP(y, h) REP(x, w) {
        if (m[y][x] != 'L') continue;
        REP(d, 4) {
            ll nx = x + dx[d];
            ll ny = y + dy[d];
            if (inRange(nx, ny) && m[ny][nx] == 'C') {
                m[ny][nx] = 'W';
            }
        }
    }
    // REP(i, h) {
    //     cout << m[i] << endl;
    // }
    auto id = [&](ll x, ll y) { return y * w + x; };
    ll s = w*h, t = s+1;
    ll cnt = 0;
    REP(y, h) REP(x, w) if (m[y][x] == 'C') ++cnt;
    MaxFlow mf(t+1);
    REP(y, h) REP(x, w) {
        if (m[y][x] != 'C') continue;
        if ((x + y) % 2 != 0) continue;
        REP(d, 4) {
            ll nx = x + dx[d];
            ll ny = y + dy[d];
            if ( !inRange(nx, ny) ) continue;
            if (m[ny][nx] != 'C') continue;
            // cout << y << " " << x << " -> " << ny << " " << nx << endl;
            mf.add(id(x, y), id(nx, ny), 1);
        }
    }
    REP(y, h) REP(x, w) {
        if ((x + y) % 2 == 0) {
            mf.add(s, id(x, y), 1);
        }
        else {
            mf.add(id(x, y), t, 1);
        }
    }
    ll flow = mf.flow(s, t);
    // return 0;
    // cout << flow << endl;
    ll ans = cnt - flow;
    function<void(ll, ll)> fill = [&](ll x, ll y) {
        m[y][x] = 'X';
        REP(d, 4) {
            ll nx = x + dx[d];
            ll ny = y + dy[d];
            if (inRange(nx, ny) && m[ny][nx] == 'L') {
                fill(nx, ny);
            }
        }
    };
    REP(y, h) REP(x, w) {
        if (m[y][x] == 'L') {
            fill(x, y);
            ++ans;
        }
    }
    cout << ans << endl;
}

2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) - F: Illumination ★★★★☆

評価

2-SATに見えない良問.
dfsしてしまいWA, 同値類で分解できるじゃーんと言ってUFしてWAした.

問題

n×nのグリッドのいくつかのマスに照明がある.
照明は全部でL個あり, 照明iはx_i, y_iに配置されている.
各照明は上下または左右に距離Rまで(合計2R+1マス)照らせる.
各照明の方向をうまく決め, 問題のないようにできるか判定せよ.
問題がある ⇔ ある照明の組(照明i, 照明j) (i≠j)が存在して, 照明iと照明jが置かれた行または列が等しく, ある同じマスを照らしている
codeforces.com

制約

n \le 1000
L \le 1000

考察

f_i := 照明iを横向きに置くなら1, 縦向きに置くなら0
とすると, f_i = 1 ならば

  • i \ne j
  • y_j = y_i
  • x_i-2R \le x_j \le x_i+2R

なる照明jは縦向きでなければならない
などの制約 f_i = hoge \to f_j = fuga がたくさん出てきてこれは2-SAT.

解答

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> P;

const ll linf = 1e18;

#define FOR(i,a,b) for(ll i = (a); i < (b); ++i)
#define RFOR(i,a,b) for(ll i = (b)-1; i >= (a); --i)
#define REP(i,n) FOR(i,0,n)
#define RREP(i,n) RFOR(i,0,n)
#define EACH(i,v) for(auto&& i : v)
#define ALL(v) (v).begin(),(v).end()
#define pb push_back

template<class T>
istream& operator>>(istream& is, vector<T>& v) {
    EACH(i, v) is >> i;
    return is;
}

class UnionFind {
    vector<ll> par, h;
public:
    UnionFind(ll size) {
        par.assign(size, 0);
        h.assign(size, 0);
        REP(i, size) par[i] = i;
    }
    void unite(ll u, ll v) {
        u = root(u), v = root(v);
        if (u == v) return;
        if (h[u] > h[v]) {
            par[v] = u;
        }
        else if (h[u] < h[v]) {
            par[u] = v;
        }
        else {
            ++h[u];
            par[v] = u;
        }
    }
    bool isUnited(ll u, ll v) {
        return root(u) == root(v);
    }
    ll root(ll v) {
        if (par[v] == v) return v;
        return par[v] = root(par[v]);
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll n, R, L; cin >> n >> R >> L;
    vector<vector<ll>> m(n, vector<ll>(n, -1));
    vector<P> lumps;
    REP(i, L) {
        ll x, y; cin >> y >> x; --x, --y;
        m[y][x] = i;
        lumps.pb({x, y});
    }
    auto id = [&](ll lid, ll dir) {
        return lid * 2 + dir;
    };
    auto inRange = [&](ll x, ll y) {
        return 0 <= x && x < n && 0 <= y && y < n;
    };
    UnionFind uf(L * 2);
    REP(lid, lumps.size()) {
        P p = lumps[lid];
        // row
        FOR(dx, -2*R, 2*R+1) {
            if (dx == 0) continue;
            ll nx = p.first + dx;
            ll ny = p.second;
            if (!inRange(nx, ny)) continue;
            if (m[ny][nx] < 0) continue;
            ll nlid = m[ny][nx];
            assert(lid != nlid);
            uf.unite(id(lid, 0), id(nlid, 1));
            // cout << "0:" << lid << " " << "1:" << nlid << endl;
        }
        // col
        FOR(dy, -2*R, 2*R+1) {
            if (dy == 0) continue;
            ll nx = p.first;
            ll ny = p.second + dy;
            if (!inRange(nx, ny)) continue;
            if (m[ny][nx] < 0) continue;
            ll nlid = m[ny][nx];
            uf.unite(id(lid, 1), id(nlid, 0));
            // cout << "1:" << lid << " " << "0:" << nlid << endl;
        }
    }
    try {
        REP(i, L) {
            // cout << L*2 << " " << id(i,0) << " " << id(i,1) << endl;
            // cout << uf.isUnited(id(i, 0), id(i, 1)) << endl;
            if ( uf.isUnited(id(i, 0), id(i, 1)) ) throw 1;
        }
        cout << "YES" << endl;
    }
    catch (int err) {
        cout << "NO" << endl;
    }
}