Official

F - Product Equality Editorial by physics0523


\(2\) 変数を選んで掛け算をする方法は \(N^2\) 通りあります。
ただし、各変数は非常に大きいので、これらを愚直に求めるとき (オーバーフローなどを考慮せず) 多倍長演算に頼るとしても、桁数を \(d\) として \(O(N^2 d \log d)\) 程度の計算量が予想され、これでこの問題に正解することは厳しいです。

ひとまず、ある \(3\) 変数 \(p,q,r\) に対して \(p \times q = r\) となるか判定しましょう。
ここで、少し大胆な以下の判定をすることを考えます。

  • ある整数 \(x\) を選択し、 \(p \times q \equiv r\) ( \(\rm{mod}\) \(x\) ) であれば \(p \times q = r\) であると判定する。

この方法は、 \((p \times q - r) \equiv 0\) ( \(\rm{mod}\) \(x\) ) であるとき失敗します。

ここで、例えば \(x\) として \(10^9\) 以上の素数を選択することを考えます。
このとき、この判定が失敗する \(x\) は高々 \(222\) 個しかありません(理由: \(|p \times q - r| \le 10^{2000}\))。これに対して、 \(10^9\) 以上 \(2 \times 10^9\) 以下の素数は数千万個あります。

このため、実は以下の乱択アルゴリズムが正当な解法となります。

  • \(S = \) 乱択した素数の集合 ( 各値は \(10^9\) 以上 \(2 \times 10^9\) 以下の素数 ) とする。
  • \(S\) 中全ての要素 \(x\) について、 \(A_i \times A_j \equiv A_k\) ( \(\rm{mod}\) \(x\) ) が成り立てば、 \(A_i \times A_j = A_k\) と判定する。
  • 先に全ての \(A_k\) に対して \(S\) 中の各要素 \(x\)\(\rm{mod}\) を取った値を準備しておき、それらに \(A_i \times A_j\)\(\rm{mod}\) を取ったものがマッチするか判定する。

これにより、 \(O(|S| \times N^2 \log N)\) でこの問題に正解することができます。成功確率を精密に見積もることはこの解説では省略しますが、 \(|S|\) として \(20\) 程度の適当な整数を選択すると良いでしょう。

また、 \(x\) に対して素数判定を行わず、ただ適当に大きい整数を選択するだけでもこの問題に正解することができます。(そのような解法を撃墜する (不正解にするようなテストケースを用意する) ことは非常に難しいです。)

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

long long takemod(string &s,long long mod){
  long long res=0;
  for(auto &nx : s){
    res*=10;res%=mod;
    res+=(nx-'0');res%=mod;
  }
  return res;
}

int lb(vector<long long> &x,vector<vector<long long>> &lis){
  int l=0,r=lis.size()-1;
  while(l<=r){
    int te=(l+r)/2;
    if(lis[te] < x){l=te+1;}
    else{r=te-1;}
  }
  return l;
}

int ub(vector<long long> &x,vector<vector<long long>> &lis){
  int l=0,r=lis.size()-1;
  while(l<=r){
    int te=(l+r)/2;
    if(lis[te] <= x){l=te+1;}
    else{r=te-1;}
  }
  return l;
}

int main(){
  std::random_device seed_gen;
  std::mt19937_64 engine(seed_gen());

  int n;
  cin >> n;
  int ss=20;
  vector<long long> S;
  for(int i=0;i<ss;i++){
    S.push_back(1000000000+engine()%1000000000);
  }
  vector<vector<long long>> md;
  for(int i=0;i<n;i++){
    string a;
    cin >> a;
    vector<long long> cv;
    for(int j=0;j<ss;j++){
      cv.push_back(takemod(a,S[j]));
    }
    md.push_back(cv);
  }
  sort(md.begin(),md.end());

  long long res=0;
  for(int i=0;i<n;i++){
    for(int j=0;j<n;j++){
      vector<long long> cp;
      for(int k=0;k<ss;k++){
        cp.push_back((md[i][k]*md[j][k])%S[k]);
      }
      res+=ub(cp,md)-lb(cp,md);
    }
  }
  cout << res << "\n";
  return 0;
}

posted:
last update: