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


POJ 1917 Automatic Poetry

問題

"s1<s2>s3<s4>s5" という形式の文字列が与えられる。もう一つ "s..." という形式の文字列が与えられる。元の文字列からカッコを取り除いた文字列と、もう一つの文字列を "ss4s3s2s5" という形式に直したものを出力せよ。

やりかた

Javaであれば正規表現を使えるので楽。といってもC++でも難しいことはない。

以下ソース。

int main(int argc, char **argv){
  int N;
  cin >> N;
  cin.ignore();
  while(N--){
    string s, t;
    getline(cin, s);
    getline(cin, t);
    
    int idx = 0;
    string a[5];
    for(int i = 0; i < s.length(); i++){
      if(s[i] == '<' || s[i] == '>') idx++; //カッコが来るごとに文字は分かれる
      else a[idx] += s[i];
    }
    cout << a[0] << a[1] << a[2] << a[3] << a[4] << endl;
    for(int i = 0; i < t.length(); i++){
      if(t[i] == '.'){
	cout << a[3] << a[2] << a[1] << a[4] << endl;
	break;
      }else cout << t[i];
    }
  }
  return 0;
}

POJ 1384 Piggy-Bank

問題

複数種類の硬貨があり、その重さと価値が与えられる。重みの和がちょうどE-Fになるように硬貨を取り出したときの価値の和を最小化せよ。硬貨は各種類とも無尽蔵にあるとしていい。

やりかた

dp[i]:=(重みの和がiであるときの価値の最小値)でDP。硬貨jの重みをw[j]、価値をv[j]とするとdp[i + w[j]] = min(dp[i + w[j]], dp[i] + v[j])で更新できる。

以下ソース。

int dp[10002];
int v[501], w[501];
int main(int argc, char **argv){
  int T; cin >> T;
  while(T--){
    int A, B; scanf("%d %d", &A, &B);
    B -= A;
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d %d", v + i, w + i);

    FILL(dp, INF);
    dp[0] = 0;
    for(int i = 0; i < B; i++){
      for(int j = 0; j < n; j++){
	int nxt = min(i + w[j], 10001);
	dp[nxt] = min(dp[nxt], dp[i] + v[j]);
      }
    }
    if(dp[B] != INF)
      printf("The minimum amount of money in the piggy-bank is %d.\n", dp[B]);
    else 
      printf("This is impossible.\n");
  }
  return 0;
}

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;
}
Get up! 明日のSUPER ST@R!