https://codeforces.com/contest/86/problem/D
題意:n個數,m個查詢。每個查詢給出一個區間,查詢的結果是區間內每個數出現次數的平方*該數的總和。
思路:莫隊演算法。分塊,查詢排序,輸出。
總結:莫隊演算法適用於離線的情況,他的原理是將查詢按左端點分塊,塊的大小是數量的開平方。然後對查詢進行排序,塊不同,按塊id排序,在相同塊內,按右端點升序排序。這種排序規則可以讓右指標只往右邊走。這裡有一個非常重要的最佳化,就是在相鄰的塊內,右指標走到了最右邊,此時為了避免右指標需要回溯到最左邊,將下一個塊按右端點降序排序!這點非常重要。 在這種左右指標的移動規則下,直接暴力查詢即可!!太強了。
注意:塊所在id,一定是按左端點排序。 相鄰的塊一定要有個波浪線的右指標移動最佳化。
摘抄一段介紹過來:
原文連結:https://blog.csdn.net/ACpartner/article/details/75998670
莫隊演算法主要是利用了曼哈頓距離的長度關係,將每一個詢問的座標抽象為一個點,然後將詢問分塊,進行排序(分塊的原因是使得這些詢問座標的移動的曼哈頓距離達到 最小),排序之後再按照此時的順序進行左右區間的移動,而在內部的實現的過程就要看各個題目的要求了,所以在這裡主要是理解到,莫隊演算法的核心是:平面上移動曼哈頓距離最小 (利用分塊求得平面上曼哈頓距離的最小生成樹)+ 離線查詢(無法線上查詢),在解題的過程中也要想到使得每一次轉移計算答案的時間也要是O(1)的,到此,莫隊演算法也就完成。
int BLOCK_SIZE;
struct queryNode{
int l, r, id, block_id;
long long ans;
queryNode(){}
queryNode(int l_, int r_, int id_):
l(l_),
r(r_),
id(id_),
block_id(l / BLOCK_SIZE)
{}
};
void solve(){
int n, m;
cin >> n >> m;
vector<int> a(n + 1);
for (int i = 1; i <= n; ++i){
cin >> a[i];
}
BLOCK_SIZE = sqrt(m);
vector<queryNode> querys;
for (int i = 1; i <= m; ++i){
int l, r;
cin >> l >> r;
querys.emplace_back(l, r, i);
}
/*
這個最佳化至關重要
*/
sort(querys.begin(), querys.end(), [](const queryNode& a, const queryNode& b){
return a.block_id != b.block_id ? a.block_id < b.block_id : a.block_id & 1 ? a.r < b.r : a.r > b.r;
});
int l = 1, r = 0;
int s = *max_element(a.begin() + 1, a.end());
vector<int> cnt(s + 1);
long long ans = 0;
auto cal = [&](int pos, int value){
ans -= 1ll * cnt[pos] * cnt[pos] * pos;
cnt[pos] += value;
ans += 1ll * cnt[pos] * cnt[pos] * pos;
};
for (auto& q : querys){
while (l < q.l) cal(a[l], -1), l ++;
while (l > q.l) cal(a[l - 1], 1), l --;
while (r > q.r) cal(a[r], -1), r --;
while (r < q.r) cal(a[r + 1], 1), r++;
q.ans = ans;
}
sort(querys.begin(), querys.end(), [](const queryNode& a, const queryNode& b){
return a.id < b.id;
});
for (const auto& x : querys){
cout << x.ans << '\n';
}
}