題目
題目描述
因為上次在月月面前丟人了,所以華華決定開始學資訊學。十分鐘後,他就開始學樹狀陣列了。這是一道樹狀陣列的入門題:
給定一個長度為 \(N\) 的序列 \(A\) ,所有元素初值為 \(0\) 。接下來有 \(M\) 次操作或詢問:
操作:輸入格式:1 D K,將 \(A_D\) 加上 \(K\) 。
詢問:輸入格式:2 L R,詢問區間和,即 \(\sum_{i=L}^{R}A_i\) 。
華華很快就學會了樹狀陣列並透過了這道題。月月也很喜歡樹狀陣列,於是給華華出了一道進階題:
給定一個長度為 \(N\) 的序列 \(A\) ,所有元素初值為 \(0\) 。接下來有 \(M\) 次操作或詢問:
操作:輸入格式:1 D K,對於所有滿足 \(1\le i\le N\) 且 $i\equiv0 \pmod D $ 的 \(i\) ,將 \(A_i\) 加上 \(K\) 。
詢問:輸入格式:2 L R,詢問區間和,即 \(\sum_{i=L}^{R}A_i\) 。
華華是個newbie,怎麼可能會這樣的題?不過你應該會吧。
輸入描述
第一行兩個正整數 \(N\) 、\(M\) 表示序列的長度和操作詢問的總次數。
接下來M行每行三個正整數,表示一個操作或詢問。
輸出描述
對於每個詢問,輸出一個非負整數表示答案。
示例1
輸入
10 6
1 1 1
2 4 6
1 3 2
2 5 7
1 6 10
2 1 10
輸出
3
5
26
備註
\(1\le N,M\le10^5\) , \(1\le D\le N\) , \(1\le L\le R\le N\) , \(1\le K \le 10^8\)
題解
知識點:樹狀陣列,根號分治。
顯然,這道題的修改並不能轉化為可懶標記的區間修改,也沒有很好的方法轉化為單點修改。
我們可以考慮暴力最佳化的一種,根號分治。將修改操作的 \(D\) 分為兩部分,按閾值 \(B\) 劃分:
- \(D \leq B\) 時,採用標記法, 用 \(add\) 陣列表示某個 \(D\) 加了多少,複雜度 \(O(1)\) 。
- \(D > B\) 時,採用暴力修改法,倍增修改樹狀陣列 \(x \equiv 0 \pmod D\) 的點,複雜度 \(O\left( \dfrac{n}{B} \log n \right)\) 。
修改總體複雜度為 \(O\left( \dfrac{n}{B} \log n \right)\) 。
同時,查詢操作也要隨之改變:
- \(D \leq B\) 部分,暴力累和每個 \(D\) 的貢獻,即 \(\displaystyle \sum_{i=1}^B add_i \cdot \left( \left \lfloor \frac{r}{i} \right \rfloor - \left \lfloor \frac{l-1}{i} \right \rfloor \right)\) ,複雜度 \(O(B)\)。
- \(D>B\) 部分,直接查詢樹狀陣列即可,複雜度 \(O(\log n)\) 。
查詢總體複雜度為 \(O(B + \log n)\) 。
我們嘗試平衡查詢和修改的複雜度。假設 \(B\) 能使 \(\log n\) 被忽略,則需要滿足 $ \dfrac{n}{B} \log n = B$ ,解得 \(B = \sqrt{n \log n}\) 。因此, \(B = \sqrt{n \log n}\) 是我們所需要的閾值,其能使總體複雜度為 \(O(\sqrt{n \log n})\) 。
實際上,這道題用理論最優閾值時間不是最優的,用 \(B = \sqrt n\) 快將近一倍,可能由於資料的 \(D\) 普遍較小,使得查詢代價上升較明顯。
這裡採用 \(B = \sqrt n\) 閾值。
時間複雜度 \(O(m\sqrt{n} \log n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template<class T>
class Fenwick {
int n;
vector<T> node;
public:
Fenwick(int _n = 0) { init(_n); }
void init(int _n) {
n = _n;
node.assign(n + 1, T::e());
}
void update(int x, T val) { for (int i = x;i <= n;i += i & -i) node[i] += val; }
T query(int x) {
T ans = T::e();
for (int i = x;i >= 1;i -= i & -i) ans += node[i];
return ans;
}
T query(int l, int r) { return query(r) - query(l - 1); }
};
struct T {
ll sum;
static T e() { return { 0 }; }
T &operator+=(const T &x) { return sum += x.sum, *this; }
friend T operator-(const T &a, const T &b) { return { a.sum - b.sum }; }
};
ll add[100007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
Fenwick<T> fw(n);
for (int i = 1;i <= m;i++) {
int op;
cin >> op;
if (op == 1) {
int d, k;
cin >> d >> k;
if (d * d <= n) add[d] += k;
else for (int i = d;i <= n;i += d) fw.update(i, { k });
}
else {
int l, r;
cin >> l >> r;
ll ans = fw.query(l, r).sum;
for (int i = 1;i * i <= n;i++) ans += add[i] * (r / i - (l - 1) / i);
cout << ans << '\n';
}
}
return 0;
}