解いた問題のソースコードと解説など。


POJ 1606 Jugs

問題

ビーカーAとBの容量Ca、Cbが与えられる。ビーカーに水を入れる操作を行って、どちらかのビーカーに入っている水がNになるようにしたい。行ってよい操作は以下の6種類である。

  • Aいっぱいに水を入れる
  • Bいっぱいに水を入れる
  • Aを空にする
  • Bを空にする
  • Aの水をBに入れる(Aが空になるかBが満杯になるまで)
  • Bの水をAに入れる(Bが空になるかAが満杯になるまで)

かならず達成できるような入力が与えられるので達成するまでの操作を出力せよ。達成方法が複数ある場合は任意の1パターンを出力せよ。

やりかた

AとBのビーカーの水の量を状態として持たせたメモ化再帰で十分通った。

以下ソース。

int ca, cb, n;
bool memo[1001][1001];
deque<int> ans;

void rec(int a, int b, deque<int> t /* 操作の履歴 */){
  if(a == n || b == n){
    ans = t;
    return;
  }
  if(!ans.empty() || memo[a][b]) return;
  memo[a][b] = true;
  if(a != ca){
    t.push_back(0);
    rec(ca, b, t); // fill A
    t.pop_back();
  }
  if(b != cb){
    t.push_back(1);
    rec(a, cb, t); // fill B
    t.pop_back();
  }
  if(a != 0){
    t.push_back(2);
    rec(0, b, t); // empty A
    t.pop_back();
    t.push_back(4);
    int chg = min(a, cb - b);
    rec(a - chg, b + chg, t); //pour A B
    t.pop_back();
  }
  if(b != 0){
    t.push_back(3);
    rec(a, 0, t); // empty B
    t.pop_back();
    t.push_back(5);
    int chg = min(b, ca - a);
    rec(a + chg, b, t); //pour B A
    t.pop_back();
  }
  return;
}
string str[] = {
  "fill A", "fill B", "empty A", "empty B", 
  "pour A B", "pour B A",
};
int main(int argc, char **argv){
  while(cin >> ca >> cb >> n){
    FILL(memo, false);
    ans.clear();
    rec(0, 0, deque<int>());
    deque<int>::iterator it = ans.begin();
    for(; it != ans.end(); it++) cout << str[*it] << endl;
    cout << "success" << endl;
  }
  return 0;
}

POJ 1107 W's Cipher

問題

アルファベットとアンダーバーからなる文字種を[a-i]、[j-r]、[s-zと_]の3グループに分ける。
文字置換による文章の暗号化を行いたい。暗号化の方法は平文の文字を一つずつ見ていき、その文字がグループiに属する時、その文字の右側を見ていって、グループiに所属するki個目の文字で置換する。
暗号文が与えられるので平文を出力せよ。

やりかた

暗号化と逆の方法をとる。その文字がグループiに属する時、その文字の左側を見ていって、グループiに所属するki個目の文字で置換すれば平文が求まる。

以下ソース。

int gr(char c){
  if('a' <= c && c <= 'i') return 0;
  if('j' <= c && c <= 'r') return 1;
  else return 2;
}
int k[3];
int main(int argc, char **argv){
  string s;
  while(cin >> k[0] >> k[1] >> k[2], k[0] + k[1] + k[2]){
    cin >> s;
    string t = s;
    for(int i = 0; i < s.size(); i++){
      int idx = i, skip = 0;
      while(1){
	idx--;
	idx = (idx + s.size()) % s.size();
	skip += (gr(s[i]) == gr(s[idx]));
	if(skip == k[gr(s[i])]){
	  t[i] = s[idx];
	  break;
	}
      }
    }
    cout << t << endl;
  }
  return 0;
}

POJ 1753 Flip Game

問題

4x4のオセロの盤面が与えられる。盤上の石を一つひっくり返すと4近傍の石もひっくり返るとする。すべての石が同じ色になるために必要なひっくり返す動作の最小回数を求めよ。どうひっくり返しても達成できない場合は"Impossible"を出力せよ。

やりかた

同じ石を2回以上ひっくり返す必要はないので16個の石をそれぞれひっくり返すかどうかのパターンを全列挙して調べれば十分間に合う。

以下ソース。

void flip(int &T, int idx){
  T ^= 1 << idx;
  if(idx + 4 < 16) T ^= 1 << (idx + 4);
  if(idx - 4 >= 0) T ^= 1 << (idx - 4);
  if(idx % 4 != 0) T ^= 1 << (idx - 1);
  if(idx % 4 != 3) T ^= 1 << (idx + 1);
}
int main(int argc, char **argv){
  int S = 0;
  for(int i = 0; i < 4; i++){
    string s; cin >> s;
    for(int j = 0; j < 4; j++)
      if(s[j] == 'w') S |= (1 << (4 * i + j));
  }
  int ans = INF;
  for(int M = 0; M < 1 << 16; M++){
    int T = S, cnt = 0;
    for(int i = 0; i < 16; i++){
      if(!(M >> i & 1)) continue;
      cnt++;
      flip(T, i);
    }
    if(T == 0 || (1 << 16) - 1 == T) ans = min(ans, cnt);
  }
  if(ans == INF) cout << "Impossible" << endl;
  else cout << ans << endl;
  return 0;
}

POJ 3869 Headshot

問題

リボルバー式の拳銃があり、シリンダー内の弾の装填の情報が与えられる。ロシアンルーレットで遊ぶとして、どの薬室からスタートしたかわからないが相手の手番では発砲しなかった。次の番はあなたで、そのまま打つか、シリンダーをランダムに回転させてから打つか選ぶことができる。どちらが発砲しない可能性が高いか答えよ。

やりかた

やるだけ
打つ前にランダムに回転させると、発砲しない確率は(0の数)/(薬室の数)になる。一方そのまま打つ場合では、相手が打ったあとなので銃身の薬室はこの時点で0という状況下である。その次の薬室が1なら発砲してしまうし、0なら発砲しない。なので発砲しない確率は(00という並びの個数/0の個数)になる。この大小を比べる。

以下ソース。

int main(int argc, char **argv){
  string s;
  while(cin >> s){
    int zero = count(ALL(s), '0');
    
    int condition = 0, situation = 0;
    for(int i = 0; i < s.size(); i++){
      if(s[i] == '0'){
	condition++;
	if(s[(i + 1) % s.size()] == '0') situation++;
      }
    }
    if(zero * condition > situation * s.size()) 
      cout << "ROTATE" << endl;
    else if(zero * condition < situation * s.size())
      cout << "SHOOT" << endl;
    else
      cout << "EQUAL" << endl;
  }
  return 0;
}

POJ 2626 Chess

問題

チェス大会のチーム編成を考えたい。各チェスプレーヤについて、白チームとして戦った場合の強さと黒チームとして戦った場合の強さの2つが与えられる。30人以上のプレーヤの強さが与えられる。これらプレーヤから選抜で白チーム15人、黒チーム15人の編成を行うとき、強さの総和を最大化せよ。ただし、一人が両チームに所属することはできない。

やりかた

DP。
dp[i][w][b]:=(i人目まで見たときに白チームにw人、黒チームにb人選抜したときの最大値)で動的計画法

以下ソース。

int dp[1010][16][16];
vector<pair<int, int> > p;
int rec(int idx, int w, int b){
  if(idx == p.size()){
    if(w == 15 && b == 15) return 0;
    else return -INF;
  }
  if(dp[idx][w][b] != -1) return dp[idx][w][b];
  
  int ret = -INF;
  ret = max(ret, rec(idx + 1, w, b));
  if(w < 15) ret = max(ret, rec(idx + 1, w + 1, b) + p[idx].first);
  if(b < 15) ret = max(ret, rec(idx + 1, w, b + 1) + p[idx].second);
  return dp[idx][w][b] = ret;
}

int main(int argc, char **argv){
  int a, b;
  while(cin >> a >> b)
    p.push_back(make_pair(a, b));
  FILL(dp, -1);
  cout << rec(0, 0, 0) << endl;
  return 0;
}

POJ 2253 Frogger

問題

x,y座標が幾つか与えられる。0番目の点から1番目の点に向かって、直接、もしくはその他の点を経由しながら移動する。点から点へのジャンプにかかるコストは点と点のユークリッド距離で表される。移動中にかかる最長のコストを最小化せよ。

やりかた

二分探索で候補となるコストを定めて、そのコストで移動可能かチェックした。チェックの仕方は、候補となるコスト以下のジャンプ(辺)をすべて列挙し、その辺の移動で0番目から1番目に移動できるか調べる方法をとった。

以下汚いソース。

struct path{
  int from, to;
  double d;
  path(){}
  path(int from, int to, double d) : from(from), to(to), d(d) {}
  bool operator<(const path &r)const{
    return d < r.d;
  }
};

int main(int argc, char **argv){
  int N;
  int idx = 0;
  while(scanf("%d", &N), N){
    int x[200], y[200];
    for(int i = 0; i < N; i++) cin >> x[i] >> y[i];
    
    vector<path> pa;
    for(int i = 0; i < N; i++)
      for(int j = i; j < N; j++)
	pa.push_back(path(i, j, sqrt((x[i] - x[j]) * (x[i] - x[j]) + 
				     (y[i] - y[j]) * (y[i] - y[j]))));
    sort(ALL(pa));
    
    double ub = 1e10, lb = 0.0;
    for(int i = 0; i < 100; i++){
      double mid = (ub + lb) * 0.5;
      bool d[200][200];
      FILL(d, false);
      for(int i = 0; i < pa.size(); i++)
	if(mid >= pa[i].d + EPS) 
	  d[pa[i].from][pa[i].to] = d[pa[i].to][pa[i].from] = true;
      
      queue<int> Q;
      Q.push(0);
      vector<bool> vis(N, false);
      vis[0] = true;
      bool reach = false;
      while(!Q.empty()){
	int p = Q.front(); Q.pop();
	if(p == 1){
	  reach = true;
	  break;
	}
	for(int i = 0; i < N; i++){
	  if(d[p][i] && !vis[i]){
	    Q.push(i);
	    vis[i] = true;
	  }
	}
      }
      if(reach) ub = mid;
      else lb = mid;
    }
    printf("Scenario #%d\nFrog Distance = %.3f\n\n", ++idx, ub);
  }
  return 0;
}

POJ 2033 Alphacode

問題

アルファベット大文字からなる平文に対し、A=1、B=2、…、Z=26と置き換える暗号化を行う。この暗号文は一意に復号化できない可能性がある。暗号文が与えられるので何通りの復号化があるか求めよ。

やりかた

DP。
dp[i]:=(i桁目までの復号化パターン数)とすると、今見ている桁だけ使った置換と、一つ前の桁も使った置換がありうるのでそれらの和になる。
dp[i + 1] = dp[i] + dp[i - 1]
ただし、今見ている桁が0の場合と、一つ前の桁が0の場合は例外なので注意。
以下ソース。

int main(int argc, char **argv){
  string s;
  while(cin >> s){
    if(s == "0") break;
    
    ll dp[1000000];
    FILL(dp, 0);
    dp[0] = 1;
    for(int i = 0; i < s.size(); i++){
      //今見ている桁だけ使った置換
      if(s[i] != '0') dp[i + 1] += dp[i];
      //一つ前の桁も使った置換が可能な場合
      if(i - 1 >= 0 && (s[i - 1] - '0' != 0) && 
	 (s[i] - '0') + 10 * (s[i - 1] - '0') <= 26)
	dp[i + 1] += dp[i - 1];
    }
    cout << dp[s.size()] << endl;
  }
  return 0;
}
Get up! 明日のSUPER ST@R!