1. 程式人生 > 其它 >牛客小白月賽39D - 絕望(數論 + 數學規律 + 資料結構)

牛客小白月賽39D - 絕望(數論 + 數學規律 + 資料結構)

牛客小白月賽39D - 絕望(源地址自⇔牛客

目錄

tag

⇔數論、⇔數學規律、⇔資料結構、⇔*1700左右

題意

給定一個長度為 \(N\) 的序列 \(a\) ,規定如下兩項操作:

  • 輸入 \(4\) 個整數 ” \(1\ L\ R\ X\) “ ,代表將所有 \(i \in [L,R]\) 修改為 \(i^x * a_i\) ,並輸出修改後的 \([L,R]\) 區間中的質數數量;
  • 輸入 \(3\) 個整數 ” \(2\ L\ R\)
    “ ,直接輸出 \([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\)

個數字均需要處理,那麼此時總單點操作的完整複雜度為 \(\mathcal{O}(2 * N*logN+Q*logN)\) ,平均時間複雜度即為 \(\mathcal{O}(Q*logN)\) ,所以時間複雜度可以通過。

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個人部落格,僅供學習討論