牛客小白月賽39D - 絕望(數論 + 數學規律 + 資料結構)
阿新 • • 發佈:2022-03-27
牛客小白月賽39D - 絕望(源地址自⇔牛客)
目錄tag
⇔數論、⇔數學規律、⇔資料結構、⇔*1700左右
題意
給定一個長度為 \(N\) 的序列 \(a\) ,規定如下兩項操作:
- 輸入 \(4\) 個整數 ” \(1\ L\ R\ X\) “ ,代表將所有 \(i \in [L,R]\) 修改為 \(i^x * a_i\) ,並輸出修改後的 \([L,R]\) 區間中的質數數量;
- 輸入 \(3\) 個整數 ” \(2\ L\ R\)
一共 \(Q\) 次詢問。
一組樣例,滿足 \(1≤N≤2∗10 ^5,1≤Q≤5∗10 ^5\) ,且 \(1≤a_i ≤5∗10^5,0≤x≤10\) 。注意,在操作過程中可能出現 \(a_i\ge 5*10^5\) 。
思路
賽時小結
很顯然的線段樹題目,而 \(x\) 與質數的規律也很快的找到了,然而交了一發WA之後重新推導才發現漏掉了一些特殊情況……
正解
首先,很顯然需要用到線段樹和素數篩,原因略。
我們分析題意,可以歸納出以下幾點特徵:
- 對於 \(i=1\) ,無論如何操作,不會改變原來數的特徵;
- 對於 \(i\) 為質數且 \(a_i=1,x=1\)
- 對於 \(x=0\) 的情況,不會改變原有特徵;
- 其餘所有情況,都會將區間內的質數變成合數。
依據前三條特徵,容易發現需要對單點進行操作,故我們需要使用化區間修改為單點修改的思想。在修改操作中做出相應的特判:
- 對於第一、三條特徵,直接忽視;
- 對於第二條特徵,使用額外的 \(\tt{}mp\) 陣列記錄該點 \(i\) 是否為質數,使用額外的 \(\tt{}one\) 陣列記錄該點的值是否為 \(1\) 。
對於第四條特徵,我們使用區間修改思想,直接將該區間的值置為 \(0\) ,並將額外陣列 \(\tt{}one\) 置為 \(0\) 。
由於本題第四條特徵佔了絕對多數:可以計算,一個數至多經過兩次操作就會變成合數,考慮最壞情況, \(N\)
AC程式碼
點選檢視程式碼
//====================
#define int LL
const int N = 1e6 + 7;
#define lk k << 1
#define rk k << 1 | 1
struct node{
int l, r, w, one;
}tre[4 * N];
int n, q;
//====================
vector<int> prime;
map<int, int> mp; int v[N];
void Force() {
int n = N - 5;
for(int i = 2; i <= n; i ++) {
if(v[i] == 0) {
v[i] = i;
prime.push_back(i);
}
for (auto it : prime) {
if (it > v[i] || it > n / i) break;
v[i * it] = it;
}
}
for (auto it : prime) mp[it] = 1;
}
void update(int k) {
tre[k].w = tre[lk].w + tre[rk].w;
tre[k].one = tre[lk].one | tre[rk].one;
}
void build(int l, int r, int k) {
tre[k].l = l, tre[k].r = r;
if (l == r) {
int x; cin >> x;
if (x == 1) tre[k].one = 1;
tre[k].w = mp[x];
return;
}
int m = (tre[k].l + tre[k].r) >> 1;
build(l, m, lk), build(m + 1, r, rk);
update(k);
}
int finds(int l, int r, int k) {
if (l <= tre[k].l && tre[k].r <= r) return tre[k].w;
int ans = 0;
int m = (tre[k].l + tre[k].r) >> 1;
if (l <= m) ans += finds(l, r, lk);
if (m < r) ans += finds(l, r, rk);
return ans;
}
void change_point(int l, int r, int x, int k) {
if (x == 0) return;
if (tre[k].w == 0 && tre[k].one == 0) return;
if (tre[k].l == tre[k].r) { //單點修改
int num = tre[k].l;
if (num == 1) return;
if (x == 1 && tre[k].one == 1 && mp[num] == 1) {
tre[k].one = 0;
tre[k].w = 1;
return;
}
tre[k].one = tre[k].w = 0;
return;
}
int m = (tre[k].l + tre[k].r) >> 1;
if (l <= m) change_point(l, r, x, lk);
if (m < r) change_point(l, r, x, rk);
update(k);
}
void Solve() {
cin >> n >> q;
build(1, n, 1);
for (int i = 1; i <= q; ++ i) {
int op; cin >> op;
if (op == 2) {
int l, r; cin >> l >> r;
cout << finds(l, r, 1) << endl;
}else {
int l, r, x; cin >> l >> r >> x;
change_point(l, r, x, 1);
cout << finds(l, r, 1) << endl;
}
}
}
錯誤次數
(賽時 3 次)未考慮全所有情況。
文 / WIDA
2022.03.27 成文
首發於WIDA個人部落格,僅供學習討論