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


POJ 2005 Blackjack

問題

トランプがnセット(1セットあたりジョーカーなしの52枚)与えられ、このカードデッキを使ってブラックジャックを行う。
この中からディーラーが1枚、あなたが2枚をランダムにピックアップし、場にお互いが見えるように置く。ディーラーがランダムにもう1枚引いたときにあなたが勝つ確率を求めよ。

やりかた

自分の得点はすぐ計算できる。その際にAを11として加算しても21より大きくならないならそうする。
ディーラーの得点は最初に引いたディーラーのカードがAか否かで場合分け。場合分けした上で各場合についてディーラーが引いた場合に負けとなるカードの総枚数(自分の勝ちパターン数)を計算し、ディーラーが引きうるカードの総数n * 52 - 3(全パターン数)で割れば割合となる。

以下ソース。

int p(char c){
  if(c == 'A') return 1;
  else if(isdigit(c)) return (int)(c - '0');
  else return 10;
}
int num(char c){
  if(c == 'A') return 1;
  if(c == 'K') return 13;
  if(c == 'Q') return 12;
  if(c == 'J') return 11;
  if(c == 'T') return 10;
  return (int)(c - '0');
}
int main(int argc, char **argv){
  int card[14];
  int n;
  while(cin >> n, n){
    if(n == 0) break;
    for(int i = 1; i <= 13; i++) card[i] = 4 * n;

    int dealer = 0, me = 0;
    char a, b, c;
    cin >> a >> b >> c;
    card[num(a)]--;
    card[num(b)]--;
    card[num(c)]--;
    
    me = p(b) + p(c);
    if(b == 'A' || c == 'A') me = (me + 10 <= 21) ? me + 10 : me;

    int chance = 0;
    if(a == 'A'){//ディーラーは最初にAを引いた
      for(int i = 1; i <= 13; i++){
	if(i == 1){//ディーラーは2枚目にAを引くケース
	  dealer = 12;
	  if(dealer < me && me <= 21) chance += card[i];
	}else{//ディーラーは2枚目にA以外をを引くケース
	  dealer = min(10, i) + 11;
	  if(dealer > 21) dealer -= 10;
	  if(dealer < me && me <= 21) chance += card[i];
	}
      }
      printf("%.3f\%\n\n", chance * 100.0 / (n * 52 - 3));
    }else{//ディーラーは最初にAではないカードを引いた
      for(int i = 1; i <= 13; i++){
	dealer = p(a);
	if(i == 1){//ディーラーは2枚目にAを引くケース
	  dealer += 11;
	  if(dealer > 21) dealer -= 10;
	  if(dealer < me && me <= 21) chance += card[i];
	}else{//ディーラーは2枚目にA以外を引くケース
	  dealer += min(i, 10);
	  if((dealer > 21 && me <= 21) || (dealer < me && me <= 21))
	    chance += card[i];
	}
      }
      printf("%.3f\%\n\n", chance * 100.0 / (n * 52 - 3));      
    }
  }
  return 0;
}

場合分けはもう少し削れるはず。

POJ 2418 Hardwood Species

問題

木の名前がいくつか入力される。各木の種類について、それが入力された総数のうち何%を占めているか木の名前のアルファベット順にパーセンテージで出力せよ。

やりかた

mapに木の名前と出現数を入れて総数で割って、、、というだけ。入力はgetsを使えば余裕で通る。

以下ソース。

int main(int argc, char **argv){
  char tree[256];
  map<string, int> p;
  int num = 0;
  while(gets(tree)){
    p[string(tree)]++;
    num++;
  }
  map<string, int>::iterator it = p.begin();
  for(; it != p.end(); it++)
    printf("%s %.4f\n", (it->first).c_str(), it->second * 100.0 / num);
  return 0;
}

atetubouさんによればgetlineでも通ったそうです。
PKU 2418 Hardwood Species - 日記

POJ 1730 Perfect Pth Powers

問題

整数Xが与えられる。X=b^pを満たすような整数bおよび自然数pが存在するとき、Xをp-th perfect numberという。pの最大値を求めよ。
制約: 2 \le |x| \le 2^{31}

やりかた

例えば20736=2^8*3^4=(2^2*3)^4なのでmax(p)=4となるように、Xを素因数分解して、各素因数の指数の最大公約数が答えになる。答えはせいぜい31以下なので、31以下の自然数pを大きい方から試して、素因数の指数が全部pで割り切れるような初めてのpを出力すればいい。
入力が負の数をとりうるので要注意。

以下ソース。

map<ll, int> pf(ll n){
  map<ll, int> p;
  for(ll i = 2; i * i <= n; i++){
    while(n % i == 0){
      p[i]++;
      n /= i;
    }
  }
  if(n != 1) p[n]++;
  return p;
}
int main(int argc, char **argv){
  ll n;
  while(cin >> n, n){
    map<ll, int> pr = pf(n < 0 ? -n : n);
    for(int p = 32; p >= 1; p--){
      bool check = true;
      map<ll, int>::iterator it = pr.begin();
      for(; it != pr.end(); it++){
	ll power = it->second;
        //pでべき乗数を割り切れないか、割り切れるけど正負が合わない場合は失敗とする
	if(power % p != 0 || (n < 0 && p % 2 == 0)) check = false;
      }
      if(check){
	cout << p << endl;
	break;
      }
    }
  }
  return 0;
}

負数の入力があることにしばらく気づかなかった。

POJ 2402 Palindrome Numbers

問題

自然数Nが与えられる。N <= 2e9
1から数えてN番目に出現する回文形式の整数を返せ。

やりかた

ある桁数の整数中に回文整数がいくつあるかというと、
1桁:1 ~ 9 9個
2桁:11 ~ 99 9個
3桁:101 ~ 999 90個
4桁:1001 ~9999 90個
5桁:10001 ~ 99999 900個
6桁:100001 ~ 999999 900個
といった具合にK桁の場合、9 * 10 ** ((K - 1) / 2)個存在する。
これでN番目の回文整数の桁数Kが求まる。ここから、N - (K-1桁までの回文整数の総数) = NがK桁の回文を小さい方から数えた順番(0-indexed)が求まる。
ここで例えば、桁数5の回文整数の、135番目に現れるとわかったとする。桁数5の回文整数は10001から始まり、ここから数えて135番目に現れる5桁の回文整数は、10001を半分の桁数にした100に134(1-indexedにした)を加えた234を折り返してつなげた23432となる。
桁数KのX番目を求める処理はこれをそのまま実装すればいい。

以下ソース。

string i2s(ll i){
  stringstream ss;
  ss << i;
  return ss.str();
}

int main(int argc, char **argv){
  ll r;
  while(cin >> r, r){
    ll sum = 0;
    int d;
    //桁数を求める => d
    for(d = 1; d <= 20; d++){
      ll total = 9LL * pow(10L, (d - 1) / 2);
      if(sum + total >= r) break;
      else sum += total;
    }
    r -= sum;
    ll s = pow(10LL, (d - 1) / 2) + r - 1;

    string ret = i2s(s);
    cout << ret;
    reverse(ALL(ret));
    if(d % 2 == 1)
      cout << ret.substr(1) << endl;
    else
      cout << ret << endl;
  }
  return 0;
}

POJ 3270 Cow Sorting

問題

要素のスワップを繰り返して数列を昇順にソートしたい。数列中のXとYを入れ替える時のコストをX+Yとする。ソートに必要な最小コストを求めよ。

やりかた

数列を巡回置換のまとまりに分解する。
例えば(3,5,7,4,2,1,6)であれば(3,7,1,6)(5,2)(4)となる。

すると各まとまり(列)ごとに分けて計算すれば良くなる。各列のサイズをci、最小の要素をmi、和をsumiとし、全体の数列中の最小の要素をMiとすると答えは以下の数式で表せる。
\sum_i{min(sum_i - m_i + (c_i - 1) * m_i, sum_i + m_i + (c_i + 1) * M_i)}

以下ソース。

//置換群の列挙 O(nlogn)
template<typename T>
vector<vector<int> > perm_group(vector<T> src){
  vector<T> dst = src;
  sort(dst.begin(), dst.end());

  int N = src.size();
  map<T, int> src_index, dst_index;
  for(int i = 0; i < N; i++) 
    src_index[src[i]] = i, dst_index[dst[i]] = i;
  
  vector<bool> vis(N, false);
  vector<vector<int> > ret;
  for(int i = 0; i < N; i++){
    if(vis[i]) continue;
    int start = i, idx = i;
    vector<int> perm;
    perm.push_back(start);
    vis[start] = true;
    while(src_index[dst[idx]] != start){
      idx = src_index[dst[idx]];
      perm.push_back(idx);
      vis[idx] = true;
    }
    ret.push_back(perm);
  }
  return ret;
}

int main(int argc, char **argv){
  int N;
  scanf("%d", &N);
  vector<int> s(N);
  int min1 = INF;
  for(int i = 0; i < N; i++){
    scanf("%d", &s[i]);
    min1 = min(min1, s[i]);
  }
  
  vector<vector<int> > p = perm_group(s);

  int ans = 0;
  for(int i = 0; i < p.size(); i++){
    if(p[i].size() == 1) continue;

    int min2 = INF;
    int sum = 0;
    for(int j = 0; j < p[i].size(); j++){
      sum += s[p[i][j]];
      min2 = min(min2, s[p[i][j]]);
    }
    ans += min(sum - min2 + (p[i].size() - 1) * min2, 
	       sum + min2 + (p[i].size() + 1) * min1);
  }
  printf("%d\n", ans);
  return 0;
}

POJ 2959 Ball bearings

問題

直径Dの円に内接する複数の直径dの小円を考える。小円同士の中心が少なくともs+d以上離れているように配置したい。最大でいくつの小円を配置できるか。

やりかた

配置する間隔は等間隔にする。間隔は円の個数に対して単調減少なので二分探索できる。C個置けるとすると隣り合う円の中心同士の距離は(D -d) * sin(π/C)になるのでこれがs+d以上になるかに関して二分探索する。

以下ソース。

int main(int argc, char **argv){
  int N; cin >> N;
  while(N--){
    double s, D, d;
    cin >> D >> d >> s;
    double ub = 1e10, lb = 0;
    for(int i = 0; i < 100; i++){
      double mid = (ub + lb) / 2.0;
      double dist = (D - d) * sin(3.14159265359 / mid);
      if(dist + EPS < d + s) ub = mid;
      else lb = mid;
    }
    cout << floor(ub) << endl;
  }
  return 0;
}

POJ 2437 Muddy roads

問題

一直線の道路上にN個のぬかるみがありそれが[s,e)の形で与えられる(s,eは非負整数)。なお与えられる範囲に重複はない。長さKの木板が無数にあり、これらを使ってぬかるみをすべて覆いたい。最小でいくつの木板が必要か。

やりかた

貪欲。
領域の左端から注目している領域をすべて覆うように木板を置く。置いた後の木板の右端が次のぬかるみにかかっていれば、その領域も続いて覆うように木板を置く。これをすべて覆うまで続ける。

以下ソース。

int main(int argc, char **argv){
  int N, K;
  cin >> N >> K;
  vector<pair<int, int> > m;
  for(int i = 0; i < N; i++){
    int l, r; cin >> l >> r;
    m.push_back(make_pair(l, r));
  }
  sort(ALL(m));
  
  int idx, cnt = 0, r = 0;
  for(idx = 0; idx < N;){
    r = m[idx].first;
    int c = (m[idx].second - m[idx].first + K - 1) / K; //領域idxをすべて覆うのに必要な枚数
    cnt += c;
    r += c * K;//領域の右端を移動する
    idx++;
    while(m[idx].first < r && idx < N){//領域の右端が次の領域にかかっていたらその領域も続けて覆う
      c = (m[idx].second - r + K - 1) / K;
      cnt += c;
      r += c * K;
      idx++;
    }
  }
  cout << cnt << endl;
  return 0;
}
Get up! 明日のSUPER ST@R!