AOJ 1238 True Liars

解法

人をノードと見て,x y a を辺と考えると良い.ある人を仮に divine あるいは delivish と定めると,その人と関係のある人間の性質が一意に定まる.
よって,関係のあるグループについて2通りを試して,その結果で dp をすればよい.
・dp[i][j][k] := i 番目のグループまで見て,divine が j 人であり,delivish が k 人である時の,直前のグループの状態
とする.直前のグループの状態は,代表の id と,その人の性質,そしてそのグループの分配の仕方を表す.
同じ dp[i][j][k] に到達する状態が複数あった場合,以降その状態とそこから遷移できるすべての状態を ambiguous に設定してしまう.
最後に dp.back()[p1][p2] が有効なら復元していく.

ソースコード

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

using pii = pair<int, int>;

struct edge {
    int to;
    bool yes;
};

using edges = vector<edge>;
using graph = vector<edges>;

// (divine, devilish)
pii dfs(graph const& g, int v, bool divine, vector<bool>& checked) {
    auto res = make_pair((int)divine, (int)!divine);
    checked[v] = true;
    for(auto& e : g[v]) {
        if(checked[e.to]) {
            continue;
        }
        bool next_state = (divine && e.yes || !divine && !e.yes);
        auto t = dfs(g, e.to, next_state, checked);
        res.first += t.first;
        res.second += t.second;
    }
    return res;
}

void determine(graph const& g, int v, bool divine, vector<int>& res) {
    res[v] = divine;
    for(auto& e : g[v]) {
        if(res[e.to] != -1) {
            continue;
        }
        bool next_state = (divine && e.yes || !divine && !e.yes);
        determine(g, e.to, next_state, res);
    }
}

struct data {
    int person_id;
    bool divine;
    pii each_num; // (divine, devilish)
    data() : person_id(-1), divine(false), each_num(make_pair(0, 0))
    {}
};

int main() {
    int n, p1, p2;
    while(cin >> n >> p1 >> p2, n || p1 || p2) {
        graph g(p1 + p2);
        if(n == 0) {
            if(p2 == 0) {
                for(int i = 1; i <= p1 + p2; ++i) {
                    cout << i << endl;
                }
            } else if(p1 != 0) {
                cout << "no" << endl;
                continue;
            }
            cout << "end" << endl;
            continue;
        }
        for(int i = 0; i < n; ++i) {
            int x, y;
            string a;
            cin >> x >> y >> a;
            g[x - 1].push_back(edge{y - 1, a == "yes"});
            g[y - 1].push_back(edge{x - 1, a == "yes"});
        }
        vector<vector<vector<data>>> dp(1, vector<vector<data>>(p1 + 1, vector<data>(p2 + 1)));
        dp[0][0][0].person_id = 0;
        vector<bool> checked(p1 + p2);
        for(int i = 0; i < p1 + p2; ++i) {
            auto now = dp.back();
            vector<vector<data>> nxt(p1 + 1, vector<data>(p2 + 1));
            if(!checked[i]) {
                for(int b = 0; b <= 1; ++b) {
                    data d;
                    d.person_id = i;
                    d.divine = b;
                    auto checked2 = checked;
                    d.each_num = dfs(g, i, b, checked);
                    if(b == 0) {
                        checked = checked2; // restore
                    }
                    for(int j = 0; j <= p1; ++j) {
                        for(int k = 0; k <= p2; ++k) {
                            if(now[j][k].person_id == -1) {
                                continue;
                            }
                            int nj = j + d.each_num.first, nk = k + d.each_num.second;
                            if(nj > p1 || nk > p2 || nxt[nj][nk].person_id == -2) {
                                continue;
                            }
                            if(nxt[nj][nk].person_id >= 0 || now[j][k].person_id == -2) { // ambiguous
                                nxt[nj][nk].person_id = -2;
                                continue;
                            }
                            nxt[nj][nk] = d;
                        }
                    }
                }
                dp.push_back(move(nxt));
            }
        }
        auto now = dp.back()[p1][p2];
        if(now.person_id < 0) {
            cout << "no" << endl;
        } else {
            vector<int> res(p1 + p2, -1);
            int P1 = p1, P2 = p2;
            for(int i = dp.size() - 1; i > 0; --i) {
                auto d = dp[i][P1][P2];
                determine(g, d.person_id, d.divine, res);
                P1 -= d.each_num.first;
                P2 -= d.each_num.second;
            }
            for(int i = 0; i < p1 + p2; ++i) {
                if(res[i] == 1) {
                    cout << i + 1 << endl;
                }
            }
            cout << "end" << endl;
        }
    }
}

感想

まあすぐわかると思うんですが,実装方針が良くないです.特に,dp[i][j][k] としているのが悪くて,j の値がわかれば k の値は自動的に決まるため,片方を保つ必要はありません.実装方針どころか,計算量が変わるので非常にまずいです.(今回は十分間に合いますけどね.)
また,ambiguous な状態の表現は,そうなる遷移の数を dp の値として持つことで自然に表現できるため,変な負の値を割り当てずにそうすべきだったかな.
あとは data 構造体で持つのではなく,複数の配列に分けて持ったほうがきれいかもしれない.これは好みかな?