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


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;
}

POJ 2121 Inglish-Number Translator

問題

アルファベット表記された数字を英数字に変換して表示せよ。

やりかた

数字をthousand未満、thousandの桁、millionの桁に分けて各桁を英数字に直して、桁の数字をかけて足し合わせた。

以下ソース。

int main(int argc, char **argv){
  map<string, int> p;
  p["negative"] = -1; p["zero"] = 0; p["one"] = 1; p["two"] = 2;
  p["three"] = 3; p["four"] = 4; p["five"] = 5; p["six"] = 6;
  p["seven"] = 7; p["eight"] = 8; p["nine"] = 9; p["ten"] = 10;
  p["eleven"] = 11; p["twelve"] = 12; p["thirteen"] = 13; p["fourteen"] = 14;
  p["fifteen"] = 15; p["sixteen"] = 16; p["seventeen"] = 17; p["eighteen"] = 18; 
  p["nineteen"] = 19; 
  p["twenty"] = 20; p["thirty"] = 30; p["forty"] = 40; p["fifty"] = 50; 
  p["sixty"] = 60; p["seventy"] = 70; p["eighty"] = 80; p["ninety"] = 90; 
  p["hundred"] = 100; p["thousand"] = 1000; p["million"] = 1000000;


  char c[400];
  while(gets(c)){
    string s(c);
    if(s.empty()) break;
    stringstream ss(s);
    vector<string> v;
    while(ss >> s) v.push_back(s);
    reverse(ALL(v));
    
    //桁ごとに入力を割り振る
    vector<string> num[4];
    int digit = 0;
    for(int i = 0; i < v.size(); i++){
      if(v[i] == "hundred"		&& digit < 1) digit = 1;
      else if(v[i] == "thousand"	&& digit < 2) digit = 2;
      else if(v[i] == "million"		&& digit < 3) digit = 3;
      else num[digit].push_back(v[i]);
    }

    //各桁について数字を計算
    int ans = 0;
    int sign = 1;
    for(int i = 0; i < 4; i++){
      int base = 1, val = 0;
      for(int j = 0; j < num[i].size(); j++){
	if(p[num[i][j]] == -1) sign = -1;
	else if(p[num[i][j]] < 100){
	  val += base * p[num[i][j]];
	}else{
	  base = p[num[i][j]];
	}
      }
      if(i == 0) ans += val;      
      if(i == 1) ans += 100 * val;
      if(i == 2) ans += 1000 * val;
      if(i == 3) ans += 1000000 * val;
    }
    cout << sign * ans << endl;
  }
  return 0;
}

POJ 1265 Area

問題

ロボットが2次元座標上で原点から出発して左回りに動いて再び原点に戻ってくる。動作は格子点から格子点への直線運動の繰り返しで行われ、毎回の移動量dx, dyが入力として与えられる。この一周の動作で囲われた領域の内部にある格子点の総数、動作した直線上の格子点の総数、領域の面積を求めよ。なおロボットは過去に自分が通過した線を交差したり、接触するような動作はしない。

やりかた

ある直線動作(線分)の上にある格子点数はgcd(dx, dy)+1で求まる。なので一連の動作全体では\sum_i gcd(dx_i, dy_i)となる。
領域は多角形になるので多角形の面積は頂点の座標(位置ベクトル)が分かれば\frac{1}{2}|\sum_i (x_iy_{i+1} - x_{i+1}y_i)|で求まる。(ただしi=M-1のときi+1は0にする)
辺上の格子点数と領域の面積がわかれば、領域内の格子点数についてはピックの定理からすぐ求まる。

以下ソース。

int gcd(int a, int b){return b == 0 ? a : gcd(b, a % b);}
int main(int argc, char **argv){
  int N;
  scanf("%d", &N);
  for(int t = 1; t <= N; t++){
    int x[101], y[101];
    int M;
    scanf("%d", &M);
    for(int i = 0; i < M; i++) scanf("%d %d", &x[i], &y[i]);

    int E = 0;
    double A = 0.0;
    for(int i = 0; i < M; i++) E += gcd(abs(x[i]), abs(y[i]));
    for(int i = 1; i < M; i++) x[i] += x[i - 1], y[i] += y[i - 1];//方向ベクトルから位置ベクトルに変換
    for(int i = 0; i < M; i++)
      A += 0.5 * (x[i] * y[(i + 1) % M] - y[i] * x[(i + 1) % M]);
    A = fabs(A);
    printf("Scenario #%d:\n", t);
    printf("%d %d %.1f\n\n", (int)(A + 1 - 0.5 * E), E, A);
  }
  return 0;
}

POJ 2537 Tight words

問題

[0...k](0<= k <= 9)の文字を使って長さnの文字列を作る。隣り合った文字の差がすべて1以下であるとき、その文字列をtightという。全文字列中のうちのtightな文字列の割合を百分率で求めよ。

やりかた

DP。
dp[i][j]:=(長さiの文字列で最後の文字がjであるようなtightな文字列が発生する割合(百分率))とする。dpでtightな文字列の総パターン数を求めて(k+1)^nで割ることもできるが、C++では多倍長整数を使わないとできないのでここでは割合のまま計算した。

以下ソース。

double dp[101][10];
int main(int argc, char **argv){
  int k, n;
  while(scanf("%d %d", &k, &n) != EOF){
    FILL(dp, 0);
    for(int i = 0; i < 10; i++) dp[1][i] = 100;
    for(int i = 1; i < n; i++){
      for(int j = 0; j <= k; j++){
	if(j - 1 >= 0) dp[i + 1][j] += dp[i][j - 1] / (k + 1);
	if(j + 1 <= k) dp[i + 1][j] += dp[i][j + 1] / (k + 1);
	dp[i + 1][j] += dp[i][j] / (k + 1);
      }
    }
    double ans = 0;
    for(int i = 0; i <= k; i++) ans += dp[n][i];
    printf("%.5f\n", ans / (k + 1));
  }
  return 0;
}

POJ 2954 Triangle

問題

3つの格子点の座標が与えられる。この格子点からなる三角形の内部にある格子点数を求めよ。ただし、辺上の格子点は数えない。また、与えられる三角形の面積は必ず0より大きい。

やりかた

ピックの定理を使った。

(ピックの定理)
頂点がすべて格子点上にあるような多角形の面積Sは、多角形内部(辺上を除く)にある格子点数をa、辺上にある格子点数をbとした時、以下の式で求められる。
S = a + \frac{1}{2}b - 1

以下ソース。

int gcd(int a, int b){ return b == 0 ? a : gcd(b, a % b); }

int main(int argc, char **argv){
  int x1, y1, x2, y2, x3, y3;
  while(cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3){
    if(x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0 && x3 == 0 & y3 == 0) break;
    //辺上の格子点数
    int b = gcd(abs(x1 - x2), abs(y1 - y2)) + gcd(abs(x2 - x3), abs(y2 - y3)) + gcd(abs(x3 - x1), abs(y3 - y1));
    int S2 = abs((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1));
    cout << (S2 + 2 - b) / 2 << endl;
  }
  return 0;
}

POJ 2295 A DP Problem

問題

'x'を変数とする1次方程式が文字列形式で与えられる。この方程式をxについて解き、解を床関数にかけたものを出力せよ。ただし解が不定の場合は"IDENTITY"を、不能の場合は"IMPOSSIBLE"を出力せよ。なお入力文字列は空白を含まない。

やりかた

単にやるだけで、方程式を左辺と右辺に分け、左辺のxの係数と定数、右辺のxの係数と定数をそれぞれ求めて、最終的に解を求めた。

以下ソース。

void strReplace(string &str, const string &from, const string &to){
  string::size_type pos = 0;
  while((pos = str.find(from, pos)) != string::npos){
    str.replace(pos, from.length(), to);
    pos += to.length();
  }
}

int s2i(string s){
  stringstream ss(s);
  int x; ss >> x; return x;
}

void proc(string s, int &coef, int &cons){
  stringstream ss(s);
  string tmp;
  while(ss >> tmp){
    if(tmp[tmp.size() - 1] == 'x'){
      if(tmp == "x") coef += 1;
      else if(tmp == "-x") coef += -1;
      else coef += s2i(tmp.substr(0, tmp.size() - 1));
    }
    else cons += s2i(tmp);
  }
  return;
}

int main(int argc, char **argv){
  int t; cin >> t;
  while(t--){
    string s;
    cin >> s;
    strReplace(s, "=", " ");
    stringstream ss(s);

    //式を左辺と右辺にわける
    string l, r;
    ss >> l >> r; 

    //項ごとに空白を入れて分けておく
    strReplace(l, "-", " -");
    strReplace(l, "+", " ");
    strReplace(r, "-", " -");
    strReplace(r, "+", " ");

    int lcoef = 0, lcons = 0, rcoef = 0, rcons = 0;
    proc(l, lcoef, lcons);
    proc(r, rcoef, rcons);

    if(lcoef == rcoef)
      cout << (lcons == rcons ? "IDENTITY" : "IMPOSSIBLE") << endl;
    else
      cout << (int)floor((rcons - lcons) * 1.0 / (lcoef - rcoef)) << endl;
  }
  return 0;
}

xを含む項の係数を求めるとき、xと-xが例外的な表記になるので注意。といっても何も難しくないけど。

Get up! 明日のSUPER ST@R!