题目来源:BZOJ 2038
思路:
莫队算法。
先考虑计算答案的表达式,如果一种颜色
xi
有
yi
个,那么,在一段
[l,r]
的区间中
分式下面的值为常数,所以说我们现在要求的就是分子的部分。
(下面转自hzwer的博客)
如果我们已知
[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
与
2.
i
与
3.
i
与
于是就是
O(n1.5)
了
代码:
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cmath>
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;
}