Hướng dẫn giải của Bedao Regular Contest 17 - PHIQUERIES


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Tác giả: hohohaha

Trước hết ta có công thức tính nhanh hàm ~\phi~. Với ~V = p_1^{e_1}p_2^{e_2}\ldots p_k^{e_k}~ (~p_1, p_2, \ldots, p_k~ là các ước nguyên tố phân biệt của ~V~), ~\phi (V) = (p_1 - 1) p_1^{e_1 - 1} (p2 - 1) p_2^{e_2 - 1}\ldots (p_k - 1) p_k^{e_k - 1}~ ~\phi (V) = \Pi_{i=1}^{k} \frac{p_i - 1}{p_i} {p_i}^{e_i}~ ~\phi (V) = V \times \Pi_{i=1}^{k} \frac{p_i - 1}{p_i}~

Xét trường hợp ~M~ là số nguyên tố. Áp dụng công thức trên, với mỗi query tính ~\phi(\Pi_{i=l}^r i)~ ta có ~\phi (Pi_{i=l}^r i) = (\Pi_{i=l}^r i) (\Pi_{p \in P_{l, r}} \frac{p - 1}{p})~ với ~P_{l, r}~ là tập các số nguyên tố là ước của ít nhất một số trong khoảng từ ~l~ đến ~r~.

Để tính tích ~\Pi_{p \in P} \frac{p - 1}{p}~, ta có thể lưu các giá trị này trong một cây phân đoạn (segment tree) và duyệt các query ~(l, r)~ theo thứ tự ~r~ tăng dần. Khi ~r = r_0~, giá trị ở vị trí ~i~ trên cây phân đoạn là tích ~\Pi_{p | i \% p = 0, i + p > r_0} \frac{p - 1}{p}~. Khi đi từ ~r = r_0 - 1~ sang ~r = r_0~, với mỗi ước nguyên tố ~p~ của ~r_0~, ta nhân giá trị ở vị trí ~r_0~ trên cây phân đoạn với ~\frac{p - 1}{p}~ và chia giá trị ở ~r_0 - p~ cho ~\frac{p - 1}{p}~. Sau đó với mỗi query ~(l, r_0)~ ta chỉ cần tính tích đoạn ~(l, r_0)~ trên cây phân đoạn.

Với ~M~ không phải số nguyên tố, ta có thể tính riêng tích trên cho các số nguyên tố ~p~ là ước của ~M~, sao đó nhân với tích cho các số nguyên tố không phải ước của ~M~.

Ngoài ra để tránh chia phép chia cho ~\frac{p - 1}{p}~ module ~M~ (vì ~p - 1~ và ~M~ có thể không nguyên tố cùng nhau), với mỗi vị trí ~i~ trên cây phân đoạn ta có thể duy trì ~V^1_i = \Pi_{p | i \% p = 0, i + p > r_0} p~. Lưu ý rằng nếu biết ~\Pi_{p | i \% p = 0, i + p > r_0} p~ ta có thể tính được ~V^0_i = \Pi_{p | i \% p = 0, i + p > r_0} \frac{p - 1}{p}~, giá trị ở vị trí ~i~ trên cây phân đoạn. Vì vậy ta có thể tính trước ~V^1_i~ cho ~10^6~ giá trị có thể của ~V^0_i~. Trong quá trình duyệt, khi di chuyển từ ~r = r_0 - 1~ sang ~r = r_0~, ta chỉ cần nhân ~V^1_{r_0}~ với p và chia ~V^1_{r_0 - p}~ cho p, sau đó cập nhật ~V^0_{r_0}~ và ~V^0_{r_0 - p}~ trên cây phân đoạn.

Độ phức tạp ~O(10^6 * log(10^6) * log(log(10^6)) + Q * log(10^6))~ với ~Q~ là số query.

Code mẫu

#include <bits/stdc++.h>

using namespace std;

// #define int long long

const int maxval = 1e6 + 6, maxq = 1e6 + 6; 

int q; 
int m; 
int phi_m; 
unordered_map<int, int> factors[maxval], factors_m; 
int pref_prod_common_factors_removed[maxval]; 

int qleft[maxq], qright[maxq]; 
vector<pair<int, int> > queries[maxval]; 

bool pre_calc_not_needed[maxval]; 
int precalced_value_0[maxval]; 
int precalced_value[maxval]; 
int cur_p_prod[maxval]; 
int it[maxval * 4 + 5]; 

int ans[maxq]; 

void put_it(int i, int l, int r, int p, int v) { 
    if(l > p || p > r) { 
        return; 
    }
    if(l == r) { 
        it[i] = v; 
        // cout << "putting: " << i << " " << l << " " << r << v << endl; 
    }
    else { 
        int mid = l + r >> 1; 
        put_it(i << 1, l, mid, p, v); 
        put_it(i << 1 | 1, mid + 1, r, p, v); 
        it[i] = 1ll * it[i << 1] * it[i << 1 | 1] % m; 
    }
}

int get_it(int i, int l, int r, int ll, int rr) { 
    int re; 
    if(l > rr || ll > r) { 
        re = 1; 
    }
    else if(ll <= l && r <= rr) { 
        re= it[i]; 
    }
    else { 
        int mid = (l + r) >> 1; 
        int v1 = get_it(i << 1, l, mid, ll, rr), v2 = get_it(i << 1 | 1, mid + 1, r, ll, rr); 
         re = 1ll * v1 * v2 % m; 
    }

    return re; 
}


int bp(int i, int j) { 
    int r = 1; 
    while(j) { 
        if(j & 1) { 
            r = 1ll * r * i % m; 
        }
        j >>= 1; 
        i = 1ll * i * i % m; 
    }
    return r; 
}

int count_prefix_power(int p, int r) { 
    int sum = 0; 
    while(r) { 
        r /= p; 
        sum += r; 
    }
    return sum; 
}


signed main() {
    for(int i = 2; i < maxval; i++) { 
      if(factors[i].size() == 0) { 
          for(int j = i; j < maxval; j += i) { 
              int t = j; 
              while(t % i == 0) { 
                    factors[j][i] += 1; 
                    t /= i; 
                }
          }

          if(1ll * i * i < maxval) { 
            for(int j = i * i; j < maxval; j += i * i) { 
                pre_calc_not_needed[j] = true; 
            }
          }
      }
  }

   cin >> m >> q; 

  for(int qi = 1; qi <= q; qi++) { 
       cin >> qleft[qi] >> qright[qi]; 
       queries[qright[qi]].emplace_back(qleft[qi], qi); 
   }


  int temp_m = m; 
  for(int p = 2; p * p <= m; p++) { 
      while(temp_m % p == 0) { 
          temp_m /= p; 
          factors_m[p]++; 
      }
  }
  if(temp_m > 1) { 
      factors_m[temp_m]++; 
  }

    phi_m = 1; 
  for(auto t: factors_m) { 
      phi_m = 1ll * phi_m * (t.first - 1) % m * bp(t.first, t.second - 1) % m; 
  }

//   cout << phi_m << endl; 


  pref_prod_common_factors_removed[0] = 1; 
  for(int i = 1; i < maxval; i++) { 
      int i_common_factors_removed = 1;
      for(auto t: factors[i]) { 
          if(factors_m.count(t.first)) continue; 
          i_common_factors_removed *= bp(t.first, t.second); 
      }
      pref_prod_common_factors_removed[i] = 1ll * pref_prod_common_factors_removed[i - 1] * i_common_factors_removed % m; 
  }

    for(int i = 1; i < maxval * 4 + 5; i++) it[i] = 1; 

    for(int i = 1; i < maxval; i++) { 
        if(factors[i].size() == 1) { 
            precalced_value_0[i] = 1ll * (i - 1) * bp(i, phi_m - 1) % m; 
        }
    }

  for(int i = 1; i < maxval; i++) { 
      if(pre_calc_not_needed[i]) continue; 

      precalced_value[i] = 1; 
      for(auto t: factors[i]) { 
          if(m % t.first == 0) continue; 
          precalced_value[i] = 1ll * precalced_value[i] * precalced_value_0[t.first] % m; 
      }
  }

    auto get_it_value = [&] (int i) -> int { 
        return precalced_value[cur_p_prod[i]]; 
    }; 

    for(int r = 1; r < maxval; r++) { 
        cur_p_prod[r] = 1;

        for(auto t: factors[r]) { 
            int p = t.first; 
            if(m % p == 0) continue; 
            cur_p_prod[r] *= p; 
            put_it(1, 1, maxval, r, get_it_value(r)); 
            if(r > p) { 
                cur_p_prod[r - p] /= p; 
                put_it(1, 1, maxval, r - p, get_it_value(r - p)); 
            }
        }

        for(auto t: queries[r]) { 
            int l = t.first, qi = t.second; 
            ans[qi] = 1ll * pref_prod_common_factors_removed[r] 
                * bp(pref_prod_common_factors_removed[l - 1], phi_m - 1) % m 
                * get_it(1, 1, maxval, l, r) % m; 

            for(auto tt: factors_m) { 
                int power = count_prefix_power(tt.first, r) - count_prefix_power(tt.first, l - 1); 
                if(power) { 
                    ans[qi] = 1ll * ans[qi] * (tt.first - 1) % m 
                        * bp(tt.first, power - 1) % m; 
                }
            }
        }
    }
    for(int qi = 1; qi <= q; qi++) { 
        cout << ans[qi] << endl; 
    }
}

Bình luận

Hãy đọc nội quy trước khi bình luận.


Không có bình luận tại thời điểm này.