Mo算法计算数组的“幂”

5

最近,我学习了莫队算法,用于对查询进行平方根分解,以加快解决某些问题的速度。

为了练习实现,我一直在尝试使用这个想法来解决D. Powerful array(Codeforces上的一个过去的竞赛问题)。该问题如下:

你有一个包含n个整数array的数组。

考虑任意子数组subarray。定义Ks为整数s在该子数组中出现的次数。子数组的定义为所有整数sKs*Ks*s之和(注意,只有这些项不为零的正数个)。

回答q个查询。在每个查询中,给定两个整数lr,计算subarray

它成立:

limits1
limits2
limits3

使用Mo算法,我编写了解决这个问题的离线代码,时间复杂度如complexity所示。我确信这个算法可以解决这个问题,并且其他人的已接受的代码也使用了类似的算法。
然而,我的代码超时了
以下是我编写的代码:
#include <ios>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <utility>
#include <map>

int sqt;
long long int ans = 0;
long long int arr[200005] = {};
long long int cnt[1000005] = {};
long long int tans[200005] = {};

struct el
{
    int l, r, in;
};

bool cmp(const el &x, const el &y)
{
    if (x.l/sqt != y.l/sqt)
        return x.l/sqt < y.l/sqt;
    return x.r < y.r;
}

el qr[200005];

int main()
{
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    std::cout.tie(NULL);
    int n, q, a, b;
    std::cin >> n >> q;
    sqt = sqrt((double)(n))+27;
    for (int i = 0; i < n; i++)
        std::cin >> arr[i];
    for (int i = 0; i < q; i++)
    {
        std::cin >> a >> b;
        a--; b--;
        qr[i].l = a;
        qr[i].r = b;
        qr[i].in = i;
    }
    std::sort(qr, qr+q, cmp);

    int li = 0; //left iterator
    int ri = 0; //right iterator
    ans = arr[0];
    cnt[arr[0]]++;

    for (int i = 0; i < q; i++)
    {
        while (li < qr[i].l)
        {
            ans -= cnt[arr[li]]*cnt[arr[li]]*arr[li];
            cnt[arr[li]]--;
            ans += cnt[arr[li]]*cnt[arr[li]]*arr[li];
            li++;
        }
        while (li > qr[i].l)
        {
            li--;
            ans -= cnt[arr[li]]*cnt[arr[li]]*arr[li];
            cnt[arr[li]]++;
            ans += cnt[arr[li]]*cnt[arr[li]]*arr[li];
        }
        while (ri < qr[i].r)
        {
            ri++;
            ans -= cnt[arr[ri]]*cnt[arr[ri]]*arr[ri];
            cnt[arr[ri]]++;
            ans += cnt[arr[ri]]*cnt[arr[ri]]*arr[ri];
        }
        while (ri > qr[i].r)
        {
            ans -= cnt[arr[ri]]*cnt[arr[ri]]*arr[ri];
            cnt[arr[ri]]--;
            ans += cnt[arr[ri]]*cnt[arr[ri]]*arr[ri];
            ri--;
        }
        tans[qr[i].in] = ans;
    }
    for (int i = 0; i < q; i++)
        std::cout << tans[i] << '\n';
}

你能否建议一些非渐进式(或可能是渐进式)的方法来加速程序,以便通过时间限制?

我已经尝试了以下方法,但都无济于事:

  1. 使用向量而不是数组。
  2. 使用两个嵌套的对代替结构体。
  3. 仅使用一个对,然后使用映射来尝试恢复正确的答案顺序。
  4. 向(例如代码中的27)添加一些各种常数。
  5. 在结构体el内部重载<比较运算符。

我觉得我错过了一些重要的东西,因为我检查过的其他代码似乎有相当大的余地通过时间限制(约半秒)。 然而,它们似乎正在使用与我的代码相同的算法。

任何帮助都将不胜感激!


你用a、l、r和s来定义子数组的幂,但每个查询只指定了a、l和r。这个求和是在所有的s上进行吗? - jbapple
是的,它是所有s的总和。我已经把这个加到了问题中,谢谢澄清! - Robin Yu
1
您提供的描述链接http://blog.anudeep2011.com/mos-algorithm/底部写着:“注意:该代码在提交时会超时,如果添加快速I/O,则可以通过。为了保持代码清晰,已删除快速I/O。”看起来IO可能是问题所在,因为cin/cout不是很快。建议检查IO时间与计算时间,看看IO是否可能导致超时。 - Matt Jordan
2个回答

1

你可以进行强度降低优化。

    while (li < qr[i].l)
    {
        ans -= cnt[arr[li]]*cnt[arr[li]]*arr[li];
        cnt[arr[li]]--;
        ans += cnt[arr[li]]*cnt[arr[li]]*arr[li];
        li++;
    }

to

    while (li < qr[i].l)
    {
        ans -= (2*cnt[arr[li]]-1)*arr[li];
        cnt[arr[li]]--;
        li++;
    }

其他的同样如此。


似乎修改这一点可以让我的代码被接受,尽管只是勉强通过(4960毫秒)。 - Robin Yu

1
你可以修改 MO 的排序函数比较器函数 cmp。
bool cmp(const el &x, const el &y)
{
    if (x.l/sqt != y.l/sqt)
        return x.l/sqt < y.l/sqt;
    return x.r < y.r;
}

优化:

如果块数为偶数,则可以按降序排序 R ,如果块数为奇数,则可以按升序排序 R 。这将大大减少从一个块移动到另一个块时 R 指针的移动次数。

我的代码:

bool cmp(const el &x, const el &y)
{
    if (x.l/sqt != y.l/sqt)
        return x.l/sqt < y.l/sqt;
    return (x.l/sqt & 1) ?  x.r < y.r : x.r > y.r; // avoids TLE
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接