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

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;
    }
}

2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) - B: Buggy Robot ★★★☆☆

評価

良問だが若干実装疲れた.
初め, dijkstraに見えなかった.
解くのにそんなに時間がかからなかったし若干実装重いB問題かーくらいの感覚だったけど, 解いてる人数少なくて驚いた.

問題

迷路と操作列が与えられる.
操作列はUDLR(上下左右)からなる文字列である.
スタート地点にいるロボットが操作列に従って移動する.
ただし画面外に移動したりゴール後に移動したりしない.
操作列のいくつかを削除(無視)したり任意の操作を加えたりできる.
ロボットがゴールにたどり着くために操作を削除・追加する回数の最小値を求めよ.

入力例

3 3
R..
.#.
..E
LRDD

出力例

1

考察

dp[i][y][x] := 元の操作列[0,i)に変更を加えた操作列を実行したときに地点(x,y)に居るような変更回数の最小値
とすると,

  • 今の操作列に従う (コスト0, (i,y,x) → (i+1, y', x'))
  • 次の操作を無視する (コスト1, (i,y,x) → (i+1,y,x))
  • 操作を加える (コスト1, (i,y,x) → (i+1,y,x))

の遷移がある.
(i,y,x)を頂点とするとDAGとは限らず, また, 異なるコストの辺があるのでdijkstraする.

解答

#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 push_back pb

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

vector<vector<vector<ll>>> dp;

struct Node {
    ll pos, x, y, cost;
};
bool operator>(const Node& n1, const Node& n2) {
    return n1.cost > n2.cost;
}
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;
    string s; cin >> s;
    const ll n = s.size();
    dp.assign(n+1, vector<vector<ll>>(h, vector<ll>(w, linf)));
    priority_queue<Node, vector<Node>, greater<Node>> Q;
    ll gx, gy;
    REP(y, h) REP(x, w) {
        if (m[y][x] == 'R') {
            Q.push({0, x, y, 0});
            dp[0][y][x] = 0;
        }
        if (m[y][x] == 'E') {
            gx = x, gy = y;
        }
    }
    auto move = [&](ll x, ll y, char op) {
        if (x == gx && y == gy) return P(x, y);
        ll dx, dy;
        if (op == 'U') dx = 0, dy = -1;
        if (op == 'R') dx = 1, dy = 0;
        if (op == 'D') dx = 0, dy = 1;
        if (op == 'L') dx = -1, dy = 0;
        ll nx = x + dx, ny = y + dy;
        if ( !inRange(nx, ny) || m[ny][nx] == '#' ) return P(x, y);
        return P(nx, ny);
    };
    const string ops = "URDL";
    while ( !Q.empty() ) {
        Node node = Q.top(); Q.pop();
        ll pos = node.pos, x = node.x, y = node.y;
        ll cost = node.cost;
        if (cost > dp[pos][y][x]) continue;
        // cout << pos << " " << x << " " << y << " " << cost << " " << dp[pos][y][x] << endl;
        if (pos < n) {
            // follow
            P p = move(x, y, s[pos]);
            ll nx, ny; tie(nx, ny) = p;
            if (dp[pos][y][x] < dp[pos+1][ny][nx]) {
                dp[pos+1][ny][nx] = dp[pos][y][x];
                Q.push({pos+1, nx, ny, dp[pos+1][ny][nx]});
            }
            // ignore
            if (dp[pos+1][y][x] > dp[pos][y][x]+1) {
                dp[pos+1][y][x] = dp[pos][y][x]+1;
                Q.push({pos+1, x, y, dp[pos+1][y][x]});
            }
        }
        // give op
        EACH(op, ops) {
            P p = move(x, y, op);
            ll nx, ny; tie(nx, ny) = p;
            if (dp[pos][y][x]+1 < dp[pos][ny][nx]) {
                dp[pos][ny][nx] = dp[pos][y][x]+1;
                Q.push({pos, nx, ny, dp[pos][ny][nx]});
            }
        }
    }
    cout << dp[n][gy][gx] << endl;
}

iTerm2でタブの複製をしたい

コマンドや移動の履歴まで複製できれば最高だが, ここではカレントディレクトリだけの複製をした. 応用すれば "新しいタブでコマンド実行" などが容易にできる.

it2_newtab.applescript
#!/usr/bin/osascript
-- Usage: oscript it2_newtab.applescript profile command
on run argv
    if length of argv < 1
        log "Usage: it2_newtab.applescript profile [command]"
        return
    end if
    tell application "iTerm2"
        tell current window
            create tab with profile (item 1 of argv)
            if length of argv >= 2 -- with command
                tell current session
                    write text (item 2 of argv)
                end tell
            end if
        end tell
    end tell
end run

$ it2_newtab.applescript "Profile Name" "Command"のように使い,

  • 第一引数: 新しいタブで開くiTerm2のプロフィール名
  • 第二引数(optional): 起動時に実行されるコマンド

を指定する

パスを通したりエイリアスしたりしておくと便利

~/.zshrc
alias clone='it2_newtab.applescript "In visor" "cd \"`pwd`\"; clear"'

iTerm2のショートカットキーに設定しても便利
無理矢理だが, ^C, clone[Enter] と強制的に入力させるようにした
キーコードは[Key Codes.app](https://itunes.apple.com/jp/app/key-codes/id414568915?mt=12)で調べると良い
f:id:drafear:20170410180529p:plain

AppleScript標準エラー出力にいい感じに表示する方法が分からなかったので, よかったら教えてくださいmm

参考にしたサイト

www.iterm2.com
developer.apple.com

ラピゲ勉強会した

京大マイコンクラブ (KMC)で毎年新入生向けに開催されているイベント「ラピッドコーディグ祭り」に向けてゲームを作ってみた。

ラピッドコーディグ祭りはゲームを作ったことない新入生でも上回生の手厚いサポートの下で1日で1からゲームを作ることを体験していただくイベントで、略すと「ラピゲ」になるそう。

「ゲ」ってなんだよ、って思ったら、昔は「ラピッドゲームコーディング祭り」って呼ばれていてその名残みたい。

 

ラピゲではrubyでSDL2のラッパであるsdl2quickを使ってゲームを作るんだけど、そこで新入生に教えるために2時間くらいかけて軽くゲームを作ってみた。

 

ゲーム内容はシンプルで、上から降ってくる玉が下に落ちるまでにクリックで消していくエイムゲー。

色々あって配布はできないんだけど、興味があったらKMCの新歓に来てくだされ。

f:id:drafear:20170405134357p:plain

はてなブログのアイコンの画質悪くね?

自分の記事上だけでいいから、とりあえずアイコンの画質を上げたいと思って、外部のアイコンを読み込むよう、アイコンのURLを置換する処理をヘッダに加えた

Before

f:id:drafear:20170331203852p:plain:w400

After

f:id:drafear:20170331203851p:plain:w400

設定 -> [ブログタイトル] -> 設定 -> 検索エンジン最適化 -> headに要素を追加
に以下を設定(srcには画像URLを設定)

<script type="text/javascript">
window.addEventListener("DOMContentLoaded", function() {
    var icons = document.querySelectorAll(".profile-icon");
    for (var i = 0; i < icons.length; ++i) {
        if (icons[i].alt === "id:drafear") {
            icons[i].src = "http://www.paper-glasses.com/api/twipi/drafear/original";
            icons[i].style.borderRadius = "32px";
            icons[i].style.width = "64px";
            icons[i].style.height = "64px";
        }
    }
});
</script>