【BZOJ 2038】小Z的袜子

题目来源:BZOJ 2038

思路:

莫队算法。
先考虑计算答案的表达式,如果一种颜色 xi yi 个,那么,在一段 [l,r] 的区间中

ans=C2yiC2rl+1=yi!2!(yi2)!2!(rl+1)!(rl+1)!=yi(yi1)(rl+1)(rl)

分式下面的值为常数,所以说我们现在要求的就是分子的部分。

(下面转自hzwer的博客)

如果我们已知 [l,r] 的答案,能在 O(1) 时间得到 [l+1,r] 的答案以及 [l,r1] 的答案,即可使用莫队算法。时间复杂度为 O(n1.5) 。如果只能在 logn 的时间移动区间,则时间复杂度是 O(n1.5logn)
其实就是找一个数据结构支持插入、删除时维护当前答案。
这道题的话我们很容易用数组来实现,做到 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] 的答案,我们很容易通过 |ll|+|rr| 次转移内求得。
将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;
}

你可能感兴趣的:(【BZOJ 2038】小Z的袜子)