[ARC167] B - Product of Divisors

[Q] https://atcoder.jp/contests/arc167/tasks/arc167_b

2で割り切れない場合をなおすのに1時間以上かけて、脳がとろけてしまった。

考察
1. B=1のときを考える
2. Aを素因数分解する。
素因数の総積が分かったとして、それがA何回分になるかを考える。素直に全部掛け算するとあっという間にオーバーフローしてしまうので、小さくできないか考える。
3. Aの素因数のうち"適当に"素因数aを1つ選んで考える。aの指数をbとする。Aの約数の総積のうち、素因数aの指数cがわかれば、c/bが答えになるはず。

A = 12 = 2 * 2 * 3 を考える
A = {1,2,3,4,6,12}
  = 2^(0,1,2) * 3^(0,1) のバリエーションで6通り。

Aのある素因数a = 2を選ぶ。aの指数は2(b)。
Aの約数の総積 1*2*3*4*6*12 について、2の指数cはいくつになる?

c = {2の指数の総和 * 3のバリエーション}の取り方なので、
(0+1+2) * 2通り = 6(c)
c/bが答えになるので、6/2 = 3回割り切れる。

Q. 本当に素因数を適当に選んでいいの?
A. いいと思う。とりあえず手計算してみて、一般化を考えてみる。

A = 2*2*2*3*3*5を考える。

Aの約数の総積について、素因数2^X, 3^Y, 5^Z がいくつ含まれているかを調べる
  2      3     5 // 素因数
------------------
  3      2     1 // 指数
  6      3     1 // 指数の総和

2^X = {2の指数の総和(6) * 3の指数(2+1) * 5の指数(1+1)}
    = 6 * 3 * 2
    = 36
3^Y = {3の指数の総和(3) * 2の指数(3+1) * 5の指数(1+1)}
    = 3 * 4 * 2
    = 24
5^Z = {5の指数の総和(1) * 2の指数(3+1) * 3の指数(2+1)}
    = 1 * 4 * 3
    = 12

ある素因数aについて
a=2のとき、X / (2の指数) = 36 / 3 = 12
a=3のとき、Y / (3の指数) = 24 / 2 = 12
a=5のとき、X / (5の指数) = 12 / 1 = 12

どれを選んでも12回割り切れることがわかる。


////////////// 一般化 /////////////////////////
A = 2^x * 3^y * 5^zを考える
  2         3          5 // 素因数
------------------------
  x         y          z // 指数
 (x+1)*x/2  (y+1)*y/2  (z+1)*z/2   // 指数の総和

2^X = (x+1)*x/2*(y+1)*(z+1)
3^Y = (y+1)*y/2*(x+1)*(z+1)
5^Z = (z+1)*z/2*(x+1)*(y+1)

求めたい回答は X/x, Y/y, Z/zのどれか。本当に同じか整理すると、
X/x = (x+1) * (y+1) * (z+1) / 2
Y/y = (x+1) * (y+1) * (z+1) / 2
Z/z = (x+1) * (y+1) * (z+1) / 2

いずれも同じ解答になることが分かった。

4, B>1を考える。特に難しくない。
Aの約数総積で求まった指数をB倍すればいい。

A=12 B=2を考える

A = 12^2
  = 144
  = 2^4 * 3^2
  = {(2^2) * 3^1)}^2

2の指数22倍して4
3の指数12倍して2になるだけ。

2の指数部 = 2の指数の総和 * 3の指数のバリエーション
         = (0+1+2+3+4) / 2 * {0,1,2}
         = 10 / 2 * 3
         = 30

答えは 30/2 = 15回割り切れる。

////////////// 一般化 /////////////////////////
A = 2^x * 3^y * 5^zを考える
  2         3          5 // 素因数
------------------------
  x*B       y*B        z*B // 指数
 (x*B+1)*x*B/2  // 指数の総和

2^X = (x*B+1)*x*B/2*(y*B+1)*(z*B+1)

求めたい回答は X/x
X/x = (x*B+1) * (y*B+1) * (z*B+1) * B / 2

5, サンプル通るが、まだ罠があるようだ。21ケース落ちてる。

6, 壊れるサンプルを探す。A=4 B=1で変だ。どうやら2で割れないときに壊れてしまうようだ。

さっきの一般式をながめる。
X/xが解答。

X/x = (x*B+1) * (y*B+1) * (z*B+1) * B / 2
      ~~~~~~~   ~~~~~~~   ~~~~~~~   ~

壊れないときは次のルールがあるっぽい。
1) 指数部のバリエーション{(x*B+1) * (y*B+1)*…}に、偶数の取り方が1つでも含まれていれば問題ない。
2) Bが偶数なら問題ない。
3) 壊れた場合、指数部をxとするとx/2あまる。

指数部のバリエーションに偶数通りのものがなく、Bが偶数でないときに壊れて、その余りは指数部/2。

A=4 B=1のときNG
4=2^2
2の指数部: (0+2)*3/2 = 3
3/2 = 1...1 になってしまう。あまりは2/2

A=16 B=1のときNG
16=2^4
2の指数部: (0+4)*5/2 = 10
10/4 = 2...2 になってしまう。あまりは4/2

A=64 B=1のときNG
64=2^6
2の指数部: (0+6)*7/2 = 21
21/6 = 3...3 になってしまう。あまりは6/2

指数部をxとすると、あまりはいつもx/2になる。


A=12 B=1のときOK
12=2*2*3
2の指数部: (0+2)*3/2*2 = 6
6/2 = 3でセーフ // 指数部のバリエーションが偶数の箇所があれば問題ない

A=4 B=2のときOK
A=16なので、約数総積の2の指数部は10
Aの2の指数部は2なので
10/2 = 5回割り切れる。

////////////// 一般化 /////////////////////////
A = 2^x * 3^y * 5^zを考える
  2         3          5 // 素因数
------------------------
  x*B       y*B        z*B // 指数
 (x*B+1)*x*B/2  // 指数の総和

2^X = (x*B+1)*x*B/2*(y*B+1)*(z*B+1)

求めたい回答は X/x
X/x = (x*B+1) * (y*B+1) * (z*B+1) * B / 2

7, 2で割り切れず壊れる場合は、Aの約数総積の指数部cから、もとのAの指数bのうち、cからb/2を引いてから、c/bを答えれば大丈夫。あまりはb/2になる。
実装

int main() {
  MOD = 998244353;
  cincout();
  ll A, B;
  cin >> A >> B;

  // Aを素因数分解。指数の配列を作る
  vector<ll> exponents;
  {
    ll a = A;
    for (ll i = 2; i * i <= a; ++i) {
      ll ex = 0;
      while (a % i == 0) {
        a /= i;
        ++ex;
      }
      if (ex > 0) exponents.emplace_back(ex);
    }
    if (a != 1) {
      exponents.emplace_back(1);
    }
  }

  // exponents[0]で割った値を答えとする。
  mint all = 1; // exponents[1]~の総積
  bool is_div2 = false;  // 2で割り切れるかどうか
  for(ll i = 1; i < (ll)exponents.size(); ++i) {
    all *= (mint)exponents[i] * B + 1;
    if (exponents[i] % 2 == 1 && B % 2 == 1) is_div2 = true; // exponents[i] * B + 1 が偶数 B<=1e18なので判定わけ
  }
  if (B % 2 == 0) is_div2 = true;
  mint b = (mint)exponents[0] * B + 1; // 項数
  mint sum = (b - 1) * b / 2;  // 指数の総和 2^(0 + 1 +...+ b-1) 初項0, 末項b-1, 項数b
  mint ans = sum * all; // 2^X 個数ある
  // 2で割り切れない場合がある
  if (exponents[0] % 2 == 0 && !is_div2) {
    ans -= exponents[0] / 2; // 割り切れないときの余りはexponents[0]/2
  }
  ans /= exponents[0];
  cout << ans << endl;
}

https://atcoder.jp/contests/arc167/submissions/46644631


この記事が気に入ったらサポートをしてみませんか?