AOJ1238 True Liars
問題リンク True Liars
- 概要
P1人の正直者とP2人の嘘つきがいる。正直者は常に真実を言って、嘘つきは常に嘘を言う。
P1+P2人のうち、正直者はどの人物達かを特定したい。
X番の人物に問いかけ、「Y番の人は正直者か」という質問をN回する。X番の人物の返答は"yes" or "no"である。質問の返答に矛盾があるようなことは無い。
質問の内容から、正直者の人物を昇順に出力せよ。正直者を一意に特定できない場合はその旨を出力せよ。
0 <= N < 1000
0 <= P1, P2 <= 300
- 解法
3段階に分けて解きました。グルーピング→DP→DPからの解の復元 の流れです。
まず、質問から分かることですが、YESと返答した場合、XとYは同じ種族で、NOの場合、XとYは違う種族であるということが分かります。よって、人物を頂点としたグラフを考えます。グラフはいくつかの連結成分に分かれ、頂点を2色で色塗りすると、塗った色の数のペアを得ることができます。例えば、最後のサンプルで、1〜3の人物の連結成分だと(1, 2)、4〜7の人物の連結成分だと(1, 3)という具合です。得られたペアのうち、片方の数字を正直者の人数とすると、「和がP1になるようなペアのリストの数字の取り方」を考えればいいことになります。先の例でいうと、P1は4なので、(1, 2)のペアからは1、(1, 3)のペアからは3を採用すると和が4になり、なおかつこれが唯一の振り分け方なので、正直者が一意に特定できます。この振り分け方はDPで求めることができます。
dp[i][j]: i番目の森の連結成分までを考え、正直者がj人になるような割り振り方の数
この表を埋め、最終的にdp[last][P1]が1になっていれば正直者が一意に特定できます。
正直者が特定できる場合は、正直者の人物の番号をリストアップしなければなりません。
これはDP表から解を復元することができます。dp[last][P1]からdp[0][0]まで、その値が1になっているところをさかのぼればOKです。さかのぼり方を見れば、ペアのうちどちらの数字を採用したのかが分かるからです。グルーピングするときに、復元のために使う情報を各頂点に付与しておくといいと思います。
- ソース
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Scanner; import java.util.Set; //True Liars public class AOJ1238 { int m, p1, p2, n, c1, c2, idx; Set<Integer>[] yes, no; boolean[] u; int[] id, mark; void f(int k, int x){ if(u[k])return; if(x==0)c1++; else c2++; u[k] = true; id[k] = idx; mark[k] = x; for(int v:yes[k])f(v, x); for(int v:no[k])f(v, 1-x); } class R{ int s, t; public R(int s, int t) { this.s = s; this.t = t; } } @SuppressWarnings("unchecked") void run(){ Scanner sc = new Scanner(System.in); yes = new Set[600]; no = new Set[600]; u = new boolean[600]; id = new int[600]; mark = new int[600]; for(;;){ m = sc.nextInt(); p1 = sc.nextInt(); p2 = sc.nextInt(); if((m|p1|p2)==0)break; n = p1+p2; for(int i=0;i<n;i++){ yes[i] = new HashSet<Integer>(); no[i] = new HashSet<Integer>(); } while(m--!=0){ int s = sc.nextInt()-1, t = sc.nextInt()-1; String r = sc.next(); if("yes".equals(r)){ yes[s].add(t); yes[t].add(s); } else{ no[s].add(t); no[t].add(s); } } Arrays.fill(u, false); Arrays.fill(id, 0); Arrays.fill(mark, 0); List<R> list = new ArrayList<R>(); idx = 0; for(int i=0;i<n;i++){ c1 = c2 = 0; f(i, 0); if(0<c1+c2){ idx++; list.add(new R(c1, c2)); } } int N = list.size(); int[][] dp = new int[N+1][p1+1]; dp[0][0] = 1; for(int i=1;i<=N;i++){ R r = list.get(i-1); for(int j=0;j<=p1;j++){ if(r.s<=j)dp[i][j]+=dp[i-1][j-r.s]; if(r.t<=j)dp[i][j]+=dp[i-1][j-r.t]; } } if(dp[N][p1]!=1){ System.out.println("no"); continue; } List<Integer> res = new ArrayList<Integer>(); for(int i=N-1;i>=0;i--){ R r = list.get(i); if(r.s<=p1 && dp[i][p1-r.s]==1){ p1-=r.s; for(int j=0;j<n;j++)if(id[j]==i&&mark[j]==0)res.add(j); } else{ p1-=r.t; for(int j=0;j<n;j++)if(id[j]==i&&mark[j]==1)res.add(j); } } Collections.sort(res); for(int x:res)System.out.println(x+1); System.out.println("end"); } } public static void main(String[] args) { new AOJ1238().run(); } }