莫队算法。
先考虑计算答案的表达式,如果一种颜色 xi 有 yi 个,那么,在一段 [l,r] 的区间中
如果我们已知 [l,r] 的答案,能在 O(1) 时间得到 [l+1,r] 的答案以及 [l,r−1] 的答案,即可使用莫队算法。时间复杂度为 O(n1.5) 。如果只能在 logn 的时间移动区间,则时间复杂度是 O(n1.5∗logn) 。
其实就是找一个数据结构支持插入、删除时维护当前答案。
这道题的话我们很容易用数组来实现,做到 O(1) 的从 [l,r] 转移到 [l,r+1] 与 [l+1,r] 。
转移具体的代码:
void update(int p, int k){
res -= s[c[p]] * (s[c[p]] - 1);
s[c[p]] += k;
res += s[c[p]] * (s[c[p]] - 1);
}
那么莫队算法怎么做呢?
以下都是在转移为 O(1) 的基础下讨论的时间复杂度。另外由于n与m同阶,就统一写n。
如果已知 [l,r] 的答案,要求 [l′,r′] 的答案,我们很容易通过 |l–l′|+|r–r′| 次转移内求得。
将n个数分成 n−√ 块。
按区间排序,以左端点所在块内为第一关键字,右端点为第二关键字,进行排序,也就是以 (pos[l],r) 排序。
然后按这个排序直接暴力,复杂度分析是这样的:
1. i 与 i+1 在同一块内, r 单调递增,所以 r 是 O(n) 的。由于有 n0.5 块,所以这一部分时间复杂度是 n1.5 。
2. i 与 i+1 跨越一块, r 最多变化 n ,由于有 n0.5 块,所以这一部分时间复杂度是 n1.5
3. i 与 i+1 在同一块内时 l 变化不超过 n0.5 ,跨越一块也不会超过 n0.5 ,忽略 ∗2 。由于有 m 次询问(和n同级),所以时间复杂度是 n1.5
于是就是 O(n1.5) 了
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 50010;
struct node{
int l, r, id; ll a, b;
}v[maxn];
int each, n, m, res;
ll c[maxn], pos[maxn], s[maxn];
bool cmp(node a, node b){return pos[a.l] == pos[b.l] ? a.r < b.r : a.l < b.l;}
bool cmp_id(node a, node b){return a.id < b.id;}
void update(int p, int k){
res -= s[c[p]] * (s[c[p]] - 1);
s[c[p]] += k;
res += s[c[p]] * (s[c[p]] - 1);
}
int main(){
scanf("%d%d", &n, &m), each = (int)sqrt(n);
for(int i = 1; i <= n; i ++) scanf("%d", &c[i]);
for(int i = 1; i <= n; i ++) pos[i] = (i-1)/each+1;
for(int i = 1; i <= m; i ++) scanf("%d%d", &v[i].l, &v[i].r), v[i].id = i;
sort(v+1, v+1+m, cmp);
for(int i = 1, l = 1, r = 0; i <= m; i ++){
for( ; r < v[i].r; r ++) update(r+1, 1);
for( ; r > v[i].r; r --) update(r, -1);
for( ; l < v[i].l; l ++) update(l, -1);
for( ; l > v[i].l; l --) update(l-1, 1);
if(v[i].l == v[i].r){v[i].a = 0, v[i].b = 1; continue;}
v[i].a = res, v[i].b = (ll)(r-l+1)*(r-l);
ll k = __gcd(v[i].a, v[i].b);
v[i].a /= k, v[i].b /= k;
} sort(v+1, v+1+m, cmp_id);
for(int i = 1; i <= m; i ++) printf("%lld/%lld\n", v[i].a, v[i].b);
return 0;
}