Nobody is needed

Svemit發表於2024-03-27

賽時沒看 F,寫完 E 就沒時間了,唉唉,唉唉唉唉。

思路是 t 寶的程式碼。

Solution

考慮只有一組詢問 \(l = 1, r = n\) 的時候怎麼做。

此時顯然可以 dp,設 \(f_i\) 為以 \(a_i\) 結尾的合法子序列的個數。

得到方程

\[f_i = 1 + \sum_{a_j \mid a_i} f_j \]

\(1\) 是子序列中只有自己的情況。

答案即為 \(\sum f_i\)

對於原問題,考慮掃描線。

發現列舉 \(r\) 的話不是很好維護 dp 陣列,因為對於每個 \(l\) 的 dp 陣列是不一樣的,所以這裡列舉 \(l\)

假設此時列舉到 \(l\),那所有 \(dp_i\) 的值是 \([l + 1, i]\) 區間內以 \(i\) 結尾的合法子序列的數量。

\(f_l\) 初始化為 \(1\)

如果直接按之前那樣列舉的話,複雜度會炸到 \(n^2\)。考慮 \(a_l\) 能影響到的數只有 \(a_l\) 的倍數,考慮每次只列舉這些數。

這些數每個會有一個增量,根據之前的 dp,發現增量也可以 dp 算出來,只需要將方程改成 \(f_i = \sum_{a_j \mid a_i} f_i\) 即可。

最佳化這個過程,可以透過先列舉 \(a_l\) 的倍數,假設是 \(k\),此時 \(k\) 的增量已經被算出,再列舉 \(k\) 的倍數 \(k'\)\(k'\) 的增量加上 \(k\) 的增量,就能夠保證 \(a_l\) 的所有倍數能夠算出增量。

算出增量後,用樹狀陣列維護 \(f\) 陣列的區間和即可。

一輪列舉倍數是 \(O(n \ln n)\) 的,要列舉所有倍數的倍數即為 \(O(n \ln ^ 2 n)\),用樹狀陣列讓所有倍數加上增量是 \(O(n \ln n \log n)\) 的,所以總複雜度為 \(O(n \log ^ 2 n)\)

struct BIT {
	int n;
	vector<ll> c;

	BIT(int _n) {
		n = _n;
		c.resize(n + 1, 0);
	}

	void add(int x, int v) {
		for (; x <= n; x += x & -x) c[x] += v;
	}

	ll query(int x) {
		ll res = 0;
		for (; x; x -= x & -x) res += c[x];
		return res;
	}

	ll query(int l, int r) {
		l --;
		return query(r) - query(l);
	}
};

void solve() {
	int n, m;
	cin >> n >> m;
	vector<int> a(n + 1), p(n + 1);
	for (int i = 1; i <= n; i ++) {
		cin >> a[i];
		p[a[i]] = i;
	}
	
	vector<vector<pair<int, int>>> qry(n + 1);
	for (int i = 1; i <= m; i ++) {
		int l, r;
		cin >> l >> r;
		qry[l].emplace_back(r, i);
	}

	vector<ll> ans(m + 1);
	vector<ll> g(n + 1);
	BIT bit(n);

	for (int l = n; l >= 1; l --) {
		int x = a[l];
		g[x] = 1;
		for (int i = x; i <= n; i += x) {
			if (p[i] < p[x]) continue;
			for (int j = i + i; j <= n; j += i) {
				if (p[j] < p[i]) continue;
				g[j] += g[i];
			}
		}
		for (int i = x; i <= n; i += x) {
			if (p[i] < p[x]) continue;
			bit.add(p[i], g[i]);
			g[i] = 0;
		}
		for (auto [r, id] : qry[l]) ans[id] = bit.query(r);
	}
	for (int i = 1; i <= m; i ++) cout << ans[i] << " \n"[i == m];
} 

相關文章