fuu32のブログ

精進記録

CSA Round #85 Strange Transformation

問題概要

CS Academy
二つの整数A,BN個の数字が与えられる。以下の二つの操作をそれぞ れ0回以上行い、条件を満たしつつABにしたい。
操作1.A3倍する
操作2.A2で割る(ただしAが偶数の時に限る)

条件:A からBにする過程でAN個の数字全てになる。

AからBにする過程を出力。不可能なら-1を出力。

制約

0≤N≤50
1 ≤ A ≤ 10^{9}
1≤B≤10^{9}​​
A≠B

N個の数字について
1≤X​i​​≤10​^{9}
​​ A≠X_i,B≠X_i
​​ X_i≠X_j,i≠j

考えたこと

サンプルをみると何がしたいのかよくわかる。
A=24B=81N個の数字の集合をSとするとS=\{18,6\}の時出力は24,12,6,18,54,27,81のようになる。正しい操作と条件を満たしていればAは変化する過程でどのような値になってもいい。

まず、二つの操作では3を掛けるか2で割るかの操作しかしないので、A,B素因数分解した際、2,3以外の素因数とその指数は一致していなければならない。

このことは与えられるN個の数字についても同じことが言える。

次にN個の数字をどのような順番で経由していけばいいだろうか?もちろん、すべての組合せを考えるのは時間がかかりすぎる。例えば操作1のみが行える場合、Aは増加する一方であるからN個の数字は昇順に並んでいることが望ましい。また、操作2のみが行える場合、Aは減少する一方であるかからN個の数字は降順に並んでいることが望ましい。

では操作1,2が行える状況ではN個の数字はどのように並んでいればいいかを考えると、集合Sに含まれるすべてのX_i で2の指数については降順に、3の指数については昇順にソートされていればいい。

こんな感じに\{2^{6}\cdot3^{2},2^{4}\cdot3^{3},2^{2}\cdot3^{4}\}

ソートされた結果\{2^{7}\cdot3^{4},2^{6}\cdot3^{3},2^{5}\cdot3^{2}\}のようになってしまう可能性があるのでソート後に正しくソートされているかのチェックを行なっている。

X_iの並び順が決まったら、あとは\{A,ソートされたX_i,B\}となっている配列を先頭から隣り合う要素を見てその指数の差を埋め合わせるように3を掛けるか2で割るかの操作を行い値を出力していけば良い。

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstring>
#include <deque>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stdlib.h>
#include <string>
#include <utility>
#include <vector>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define loop(i, x, n) for (int i = (x); i < (n); i++)
#define all(v) (v).begin(), (v).end()
#define int long long
using namespace std;
const int MOD = 1e9 + 7;
const int INF = 1e9;
template<typename T> void cmax(T &a, T b) { a = max(a, b); }
template<typename T> void cmin(T &a, T b) { a = min(a, b); }

//素因数分解<素因数、指数>
map<int, int> prime_fac(int n) {
  map<int, int> ret;
  for (int i = 2; i * i <= n; i++) {
    while (n % i == 0) {
      ret[i]++;
      n /= i;
    }
  }
  if (n != 1) ret[n] = 1;
  return ret;
}

struct primeFac {
  //X_i,2の指数、3の指数
  int num = 0, two = 0, three = 0;
  bool operator<(primeFac x) const { return two >= x.two && three <= x.three; }
};
int n, a, b;
vector<primeFac> p(n + 2);

//2、3以外の素因数の積を計算
int cal(map<int, int> m, int x, int pos) {
  int res = x;
  for (auto &z : m) {
    if (z.first == 2) {
      int q = z.second;
      while (q--) res /= 2;
      p[pos].num = x;
      p[pos].two = z.second;
    } else if (z.first == 3) {
      int q = z.second;
      while (q--) res /= 3;
      p[pos].num = x;
      p[pos].three = z.second;
    }
  }
  return res;
}

signed main() {
  cin >> n >> a >> b;
  p.resize(n + 2);

  vector<int> x(n);
  rep(i, n) cin >> x[i];

  map<int, int> m;
  m = prime_fac(a);
  int res = cal(m, a, 0);
  m = prime_fac(b);

  if (res != cal(m, b, n + 1)) {
    cout << -1 << endl;
    return 0;
  }
  
  rep(i, n) {
    m = prime_fac(x[i]);
    if (res != cal(m, x[i], i + 1)) {
      cout << -1 << endl;
      return 0;
    }
  }
  
  sort(p.begin() + 1, p.end() - 1);
  
  rep(i, n + 1) {
    if (!(p[i] < p[i + 1])) {
      cout << -1 << endl;
      return 0;
    }
  }

  rep(i, n + 1) {
    cout << p[i].num << ' ';
    int l = p[i].num, r = p[i + 1].num;

    rep(j, abs(p[i + 1].two - p[i].two)) {
      l /= 2;
      if (l == r) break;
      cout << l << ' ';
    }
    rep(j, p[i + 1].three - p[i].three) {
      l *= 3;
      if (l == r) break;
      cout << l << ' ';
    }

    if (i == n) {
      cout << p[i + 1].num << endl;
    }
  }
  return 0;
}

感想

コードに関してはもっと綺麗に書けそう。こういう問題をすらっと解けるようになりたいね。