BZOJ - 2038: 小Z的袜子(hose) 莫队算法 模板

题目链接: 2038: [2009国家集训队]小Z的袜子(hose)

题目大意

一个长度为n的序列, 每个点有一个颜色, 用col[i]表示, q次询问, 每次求出[L, R]区间中随机选两个点的颜色相同的概率

思路

对于[L, R], 设区间内各种颜色的数量为 a,b,c , 则答案为:

a(a1)2+b(b1)2+c(c1)2(RL+1)(RL)2

a2+b2+c2+(a+b+c+)(RL+1)(RL)

a2+b2+c2+(RL+1)(RL+1)(RL)

所以, 我们需要做的是快速求出区间内所有颜色数量的平方和
因为无法用线段树一类的数据结构进行处理, 所以需要用到传说中的莫队算法(Mo’s Algorithm)

莫队算法

莫队算法可以高效处理这类对大量区间进行查询, 通过对所有查询的区间进行离线排序处理, 使排序能够根据上一次询问的答案快速求出下一次询问的答案, 使得整体复杂度大大下降
如果知道了区间[L, R]的情况, 我们可以快速( O(1) )求出相邻区间([L-1, R], [L+1, R], [L, R-1], [L, R+1])的答案, 那么对于大量的询问, 我们按某个顺序一次处理询问, 就可以使得总体的复杂度降低到 O(nn)
莫队算法的核心就是如何得到最优的处理顺序
一种简单但是复杂度略高一点的方法:
对于整个区间[1, n], 我们先将其分成 n 个块, 给每个块依次编号, 然后, 对于每个询问区间[L, R], 先按L所在的块排序, 如果L在同一块, 再按照R的大小排序, 然后根据这个顺序离线处理所有询问, 效率可以达到 O(nn)

代码

Accepted 1472ms 3456kB C++

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;

const int maxn = 5e4 + 100;
typedef long long ll;

int n, m, block[maxn], col[maxn];
ll sum[maxn], ans;//sum[i]存储当前区间颜色i的数量, ans表示当前区间各个颜色数量和

ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

struct Query
{
    int l, r, id;
    ll nume, deno;//答案分子分母
} querys[maxn];

bool cmp(const Query & x, const Query & y)
{
    if (block[x.l] == block[y.l]) return x.r < y.r;
    return x.l < y.l;
}
bool cmp_id(const Query & x, const Query & y)
{
    return x.id < y.id;
}

void update(int p, int add)//区间向相邻区间移动, 更新答案
{
    int c = col[p];
    ans -= sum[c] * sum[c];
    sum[c] += add;
    ans += sum[c] * sum[c];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", col + i);
    int block_size = (int)sqrt(n);
    for (int i = 1; i <= n; ++i) block[i] = (i - 1) / block_size + 1;
    for (int i = 1; i <= m; ++i)
    {
        scanf("%d%d", &querys[i].l, &querys[i].r);
        querys[i].id = i;
    }

    sort(querys + 1, querys + 1 + m, cmp);
    int l = 1, r = 0;//最初为一个空区间[1, 0], ans(颜色数量平方和) = 0
    for (int i = 1; i <= m; ++i)
    {
        //更新区间, 从上一个区间得到下一个区间的情况
        for (; r < querys[i].r; ++r) update(r + 1, 1);
        for (; r > querys[i].r; --r) update(r, -1);
        for (; l < querys[i].l; ++l) update(l, -1);
        for (; l > querys[i].l; --l) update(l - 1, 1);

        //计算答案并化简分数
        if (querys[i].l == querys[i].r)
        {
            querys[i].nume = 0;
            querys[i].deno = 1;
            continue;
        }

        ll len = querys[i].r - querys[i].l + 1;
        querys[i].nume = ans - len;
        querys[i].deno = len * (len - 1);
        ll t = gcd(querys[i].nume, querys[i].deno);
        querys[i].nume /= t;
        querys[i].deno /= t;
    }

    //输出答案
    sort(querys + 1, querys + 1 + m, cmp_id);
    for (int i = 1; i <= m; ++i) printf("%lld/%lld\n", querys[i].nume, querys[i].deno);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/litmxs/article/details/78357678
今日推荐