Official

G - Cubic? Editorial by en_translator


This problem is in a sense a problem of set comparison asking “for each given segment, determine if the numbers of all prime factors are multiples of \(3\).” For such problem, hashing is a good approach.
Since we want to determine if the number of elements is a multiple of \(3\), we want to design a hash using the operation that applying three times is equivalent to identity operation. Among some of the ways to achieve it, we will introduce one of them.

First, initialize a sequence of (unsigned) integers \(H=(H_0,H_1,\dots,H_N)\) of length \(N+1\) with \(0\).

Then, for each prime factor (denoting by \(p\)), do the following operation.

  • First, choose two (unsigned) integers \(X_0\) and \(X_1\) at random for each prime factor \(p\). Then let \(X_2 = X_0 \oplus X_1\) (where \(a \oplus b\) denotes the bitwise XOR of \(a\) and \(b\)).
  • Next, if the sequence \(A\) overall has \(K\) number of prime factors \(p\), for each \(i=1,2,\dots,K\), if the \(i\)-th occurrence of prime factor \(p\) is included in \(A_j\), then update as \(H_j \leftarrow H_j \oplus X_{i \% 3}\).

By designing the hash by the procedure above, it holds that “the number of each prime factor in the segment \([l, r]\) is a multiple of \(3\)\(\Rightarrow\)\(H_l \oplus H_{l+1} \oplus \dots \oplus H_r = 0\)”. (Note that the converse does not always hold.)

After performing the operation above for all prime factors \(p\), let \(H'\) be the array of cumulative XOR, so that \(H'_i=H_0 \oplus H_1 \oplus \dots \oplus H_i\).
Then, for each query, it holds that “\(A_{L_i} \times A_{L_i+1} \times \dots A_{R_i}\) is a cubic number” \(\Rightarrow\)\(H'_{L_i-1}=H'_{R_i}\).” (Again, the converse does not hold)
The contraposition does hold: “\(H'_{L_i-1} \neq H'_{R_i}\)\(\Rightarrow\)\(A_{L_i} \times A_{L_i+1} \times \dots A_{R_i}\) is not a cubic number”.

Therefore, we can tell the following facts:

  • the misjudgment of type “\(A_{L_i} \times A_{L_i+1} \times \dots A_{R_i}\) is a cubic number but \(H'_{L_i-1} \neq H'_{R_i}\)” will never happen
  • however, the misjudgment of type “\(A_{L_i} \times A_{L_i+1} \times \dots A_{R_i}\) is not a cubic number but \(H'_{L_i-1} = H'_{R_i}\)” may happen

Now, let’s consider the probability of the judgement being accurate.

Since this hash uses XOR as the operation, the operation of one bit does not affect to another bit. Therefore, we can define the probability \(P\) for each bit that “while \(A_{L_i} \times A_{L_i+1} \times \dots A_{R_i}\) is not a cubic number, \(H_L \oplus H_{L+1} \oplus \dots \oplus H_R\) for that bit is \(0\), leading to a misjudgment” If this misjudgment occurs in all the bits for any query, then the answer will be considered wrong.
Then, the probability of being accepted for this problem is \((1-P^B)^{QT}\), where \(B\) bit is the size of hash value and \(T\) is the number of test cases.

Let’s make a rough estimate on \(B=64,P=0.5,T=100\) (each bit has one of the two digits \(0\) or \(1\), so we can roughly estimate as \(P=0.5\)). Then, the probability of being accepted is \((1-0.5^{64})^{2 \times 10^7} > 0.99999999999\), which is high enough.
Let’s make a little more pessimistic estimate. Let \(B=64,P=0.7,T=200\). Then, the probability of being accepted is \((1-0.7^{64})^{4 \times 10^7} > 0.995\); this is still a high probability.
If you are still worried, you may repeat the operation multiple times to increase the value of \(B\). Just making the value \(B\) a little larger result in a drastic increase on the probability of being accepted.

The time complexity is \(O((N+M) \log M + Q)\), where \(M=10^6\) is the maximum value of \(A\), assuming that the prime factorization of integers between \(1\) and \(M\) are done at once beforehand.

Sample code (C++):

#include<bits/stdc++.h>
#define MAX 1000005

using namespace std;

int main(){
  int n,q;
  cin >> n >> q;
  vector<int> a(n);
  for(auto &nx : a){cin >> nx;}
  vector<int> l(q),r(q);
  for(int i=0;i<q;i++){cin >> l[i] >> r[i];}
  
  uint_fast64_t seed=202202052100238523;
  mt19937_64 engine(seed);
  vector<vector<int>> pf(MAX);
  for(int i=2;i<MAX;i++){
    if(!pf[i].empty()){continue;}
    for(int j=i;j<MAX;j+=i){
      int mj=j;
      while(mj%i==0){
        pf[j].push_back(i);
        mj/=i;
      }
    }
  }
  vector<bool> res(q,true);
  for(int tr=0;tr<3;tr++){
    vector<vector<uint_fast64_t>> hs(MAX,vector<uint_fast64_t>(3,0));
    for(int i=0;i<MAX;i++){
      while(hs[i][0]==0){hs[i][0]=engine();}
      while(hs[i][1]==0 || hs[i][0]==hs[i][1]){hs[i][1]=engine();}
      hs[i][2]=(hs[i][0]^hs[i][1]);
    }
    vector<int> bk(MAX,0);
    vector<uint_fast64_t> rw(n+1,0);
    for(int i=0;i<n;i++){
      rw[i+1]=rw[i];
      for(auto &nx : pf[a[i]]){
        rw[i+1]^=hs[nx][bk[nx]%3];
        bk[nx]++;
      }
    }
    for(int i=0;i<q;i++){
      if(rw[l[i]-1]!=rw[r[i]]){res[i]=false;}
    }
  }
  for(int i=0;i<q;i++){
    if(res[i]){cout << "Yes\n";}
    else{cout << "No\n";}
  }
  return 0;
}

posted:
last update: