In mathematics you don't understand things, you just get used to them.

标签 brute force 下的文章

Description

Link.

见 Link。

Solution

前三个操作就是小清新人渣的本愿。

这里简单讲解一下。

记录两个 bitset clainv

我们考虑莫队。

cla[x]==1 表示 $x$ 这个数出现过。

inv[x]==1 表示 $100000-x$ 这个数出现过。

这两个 bitset 的维护很简单,就是在莫队的加减贡献操作里改就行了。

对于第一个减法的判断,我们的答案就是 ((cla<<x)&cla) 是否为 0。

如果为 0 的话表示应该输出有解。

正确性很好得到。

比如我们的询问是是否存在 $a,b$ 使得 $a-b=x$。

那么我们只需要存在 $a$ 以及 $a-x$ 即可。

第二个加法的判断也差不多,看作是加一个负数即可,判断是 ((cla<<(100000-x)&inv))

第三个乘法的判断直接暴力枚举因子 $i$,判断 $i,\frac{x}{i}$ 是否同时存在即可。($i\mid x$)。

由于值域和 $n,m$ 同阶,所以我们的复杂度是对的。

对于第四个操作我们直接从乘法贺过来。

枚举一个 $i$,从 1 开始,终止条件为 $i\times x\le100000$。

其中 $x$ 为当前询问给出的商。

然后直接判断是否同时存在 $i$ 和 $i\times x$ 即可。

在 $x\ge\sqrt{n}$ 的时候我们的复杂度是对的。

那么 $x<\sqrt{n}$ 的时候我们就换一种方法吧。

我们枚举一个 $x\in[1,\sqrt{100000}]$。

然后维护两个数组 premxp

在 $x$ 的枚举里面我们再枚举一个 $i\in[1,n]$。

然后 pre[i] 表示 $a_{i}$ 的上一次出现位置。

mxp[i] 扫描到 $i$ 的时候出现了满足 $a\div b=x$ 的最右位置。

维护的具体方法看注释吧。

这道题就完了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <queue>

using namespace std;

const int Maxn = 1e5 + 5, Maxs = 400 + 5, Maxv = 310;
int n, m, each, cube, isa[Maxn], cnt[Maxn], ans[Maxn], pre[Maxn], mxp[Maxn], bel[Maxn], lps[Maxs], rps[Maxs];
struct Query
{
    int t, l, r, x, id;
    Query() {}
    Query(int t1, int t2, int t3, int t4, int t5)
    {
        t = t1;
        l = t2;
        r = t3;
        x = t4;
        id = t5;
    }
};
struct Special
{
    int l, r, id;
    Special() {}
    Special(int t1, int t2, int t3)
    {
        l = t1;
        r = t2;
        id = t3;
    }
};
vector < Query > Mq; // Mo's Algorithm | Query
vector < Special > Vq[Maxn]; // Violence | Query
bitset < Maxn > cla, inv; // classic | inverse

bool cmp(const Query &one, const Query &ano)
{
    if (bel[one.l] != bel[ano.l])   return one.l < ano.l;
    else if (bel[one.l] & 1)    return one.r < ano.r;
    else    return one.r > ano.r;
}

void Plus_Cont(int x)
{
    x = isa[x];
    if (cnt[x] == 0)
    {
        cla[x] = 1;
        inv[100000 - x] = 1;
    }
    ++cnt[x];
}

void Mins_Cont(int x)
{
    x = isa[x];
    --cnt[x];
    if (cnt[x] == 0)
    {
        cla[x] = 0;
        inv[100000 - x] = 0;
    }
}

void Pare_v1()
{
    int l = 1, r = 0;
    for (auto it : Mq)
    {
        while (l > it.l)    Plus_Cont(--l);
        while (l < it.l)    Mins_Cont(l++);
        while (r > it.r)    Mins_Cont(r--);
        while (r < it.r)    Plus_Cont(++r);
        if (it.t == 1)  ans[it.id] = ((cla << it.x) & cla).any();
        else if (it.t == 2)  ans[it.id] = ((cla << (100000 - it.x)) & inv).any();
        else if (it.t == 3)
        {
            bool flag = 0;
            for (int i = 1; i * i <= it.x; ++i)
            {
                if (it.x % i == 0 && cla.test(i) && cla.test(it.x / i))
                {
                    ans[it.id] = 1;
                    flag = 1;
                    break;
                }
            }
            if (flag == 0)  ans[it.id] = 0;
        }
        else
        {
            bool flag = 0;
            for (int i = 1; i * it.x <= 100000; ++i)
            {
                if (cla.test(i) && cla.test(i * it.x))
                {
                    ans[it.id] = 1;
                    flag = 1;
                    break;
                }
            }
            if (flag == 0)    ans[it.id] = 0;
        }
    }
}

void Pare_v2()
{
    for (int x = 1; x <= Maxv; ++x)
    {
        if (Vq[x].empty())    continue;
        int now = 0;
        for (int i = 1; i <= n; ++i)
        {
            int y = isa[i];
            pre[y] = i;
            if (x * y <= 100000)    now = max(now, pre[x * y]);
            if (y % x == 0)     now = max(now, pre[y / x]);
            mxp[i] = now;
        }
        for (auto it : Vq[x])
        {
            if (it.l <= mxp[it.r])    ans[it.id] = 1;
            else    ans[it.id] = 0; 
        }
        memset(pre, 0, sizeof pre);
        memset(mxp, 0, sizeof mxp);
    }
}

char ANS[2][10] = { "yumi", "yuno" };
signed main()
{
    scanf("%d %d", &n, &m);
    each = 320;
    cube = (n - 1) / each + 1;
    for (int i = 1; i <= n; ++i)    scanf("%d", &isa[i]);
    for (int i = 1; i <= cube; ++i)
    {
        lps[i] = rps[i - 1] + 1;
        rps[i] = rps[i - 1] + each;
        if (i == cube)     rps[i] = n;
        for (int j = lps[i]; j <= rps[i]; ++j)  bel[j] = i;
    }
    for (int i = 1, t, l, r, x; i <= m; ++i)
    {
        scanf("%d %d %d %d", &t, &l, &r, &x);
        if (t == 4 && x <= Maxv)    Vq[x].emplace_back(Special(l, r, i));
        else    Mq.emplace_back(Query(t, l, r, x, i));
    }
    sort(Mq.begin(), Mq.end(), cmp);
    Pare_v1(), Pare_v2();
    for (int i = 1; i <= m; ++i)    puts(ANS[ans[i]]);
    return 0;
}

Description

Link.

无修改区间求逆序对。

Solution

首先有一个显然的 $\Theta(N\sqrt{N}\log_{2}N)$ 做法,由于过不了所以我就不废话。

其实有了 $\Theta(N\sqrt{N}\log_{2}N)$ 的过不去做法,我们就可以根据这个思路然后预处理解决问题。

我们需要处理的信息有:

  1. 散块的逆序对数量
  2. 以块为单位的区间逆序对数量

那么我们需要处理的数组就有以下几个:

  1. previous[i] 表示 $i$ 到 该块开头的逆序对数量。
  2. suffix[i] 同理。
  3. block[i][j] 表示前 $i$ 个块中 $\leq j$ 元素个数。
  4. intervals[i][j] 表示以块为单位的区间 $[i,j]$ 中的逆序对数量。

讲讲预处理方法。

  1. previous[i]suffix[i] 的处理方法都很显然,可以一直扫着然后FWT扫就行。
  2. block[i][j] 可以递推,递推式为 block[i][j]=block[i+1][j]+block[i][j-1]-block[i+1][j-1]+cont(i,j)。其中 cont(i,j) 表示计算对应块的逆序对数。
  3. intervals[i][j] 每次循环到块的开头继承上一个块的贡献即可。

计算贡献的方法很简单,归并即可。mrsrz讲得也挺清楚的,我这里就不再赘述,主要讲讲怎么卡常。

首先我们可以把主函数里的所有循环全部展开,经过实践参数传8的时候跑得比较快。

然后八聚氧先加上,luogu O2也开着。

再其次快读fread快输fwrite,这些都是卡常的标配。

然后就把能拿出来的结构体拿出来,实在不能就不管了。

然后去STL,pair vector能去就去。

然后long long开在正确的地方,不要无脑replace。

函数inline,循环register。虽然可能作用不大但是可以先加上。

然后调块长,经过无数次实践发现取150~170较为优秀。

然后加了过后发现就算rp再好也只有60pts。

然后谷歌搜索硫酸的化学式H₂SO₄,给评测机喂硫酸(idea来自SyadouHayami)。

然后本来交了5页都过不了,这下再交两次就过了。

// 省略八聚氧
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int Maxn = 1e5 + 5;
const int Maxm = 650;
const int each = 160;
int n, m, blocks, Lp[Maxn], Rp[Maxn], isa[Maxn], head[Maxn], tail[Maxn], sorted[Maxn], belong[Maxn], previous[Maxn], suffix[Maxn], block[Maxm][Maxn];
long long intervals[Maxm][Maxn];
struct Holy_Pair
{
    int first, second;

    bool operator < (const Holy_Pair& rhs) const
    {
        return first < rhs.first;
    }
} current[Maxn];
struct Fenwick_Tree
{
    int fwt[Maxn];
    
    inline void Modify(int x, int v)
    {
        for (; x + 5 <= Maxn; x += x & -x)    fwt[x] += v;
    }

    inline int Query(int x)
    {
        int ret = 0;
        for (; x; x ^= x & -x)    ret += fwt[x];
        return ret;
    }
} FWT;

#define io_e '\0'
#define io_s ' '
#define io_l '\n'
namespace Fast_IO
{
... // 省略快读
}  // namespace Fast_IO

using Fast_IO::read;
using Fast_IO::write;

inline Holy_Pair make_pair(int first, int second)
{
    Holy_Pair ret;
    ret.first = first;
    ret.second = second;
    return ret;
}

inline int Merge_Vct(int rhs[], int shr[], int szl, int szr)
{
    int itl = 1, itr = 1;
    int ret = 0, ctr = 1;
    while (itl <= szl && itr <= szr)
    {
        if (rhs[itl] < shr[itr])     ++itl, ++ctr;
        else
        {
            ret += szl - ctr + 1;
            ++itr;
        }
    }
    return ret + szr - szr;
}

inline int Merge_Idx(int st1, int st2, int sz1, int sz2)
{
    int ret = 0, id1 = st1 + 1, id2 = st2 + 1;
    sz1 += st1, sz2 += st2;
    while (id1 <= sz1 && id2 <= sz2)
    {
        if (sorted[id1] < sorted[id2])        ++id1;
        else
        {
            ret += sz1 - id1 + 1;
            ++id2;
        }
    }
    return ret;
}

inline void Behavior(int l, int r, long long &ans)
{
    int itl = 0, itr = 0;
    if (belong[l] == belong[r])
    {
        for (int i = head[belong[l]]; i <= tail[belong[r]]; ++i)
        {
            if (current[i].second >= l && current[i].second <= r)    Rp[++itr] = sorted[i];
            else if (current[i].second < l)        Lp[++itl] = sorted[i];
        }
        if (l == head[belong[l]])    ans = previous[r] - Merge_Vct(Lp, Rp, itl, itr);
        else     ans = previous[r] - previous[l - 1] - Merge_Vct(Lp, Rp, itl, itr);
    }
    else
    {
        ans = intervals[belong[l] + 1][belong[r] - 1] + previous[r] + suffix[l];
        for (int i = head[belong[l]]; i <= tail[belong[l]]; ++i)
        {
            if (current[i].second >= l)
            {
                Lp[++itl] = sorted[i];
                ans += block[belong[r] - 1][1] - block[belong[r] - 1][sorted[i]] - block[belong[l]][1] + block[belong[l]][sorted[i]];
            }
        }
        for (int i = head[belong[r]]; i <= tail[belong[r]]; ++i)
        {
            if (current[i].second <= r)
            {
                Rp[++itr] = sorted[i];
                ans += block[belong[r] - 1][sorted[i] + 1] - block[belong[l]][sorted[i] + 1];
            }
        }
        ans += Merge_Vct(Lp, Rp, itl, itr);
    }
    write(io_l, ans);
}

signed main()
{
    read(n, m), blocks = (n - 1) / each + 1;
    if (n <= 8)
    {
        for (int i = 1; i <= n; ++i)
        {
            read(isa[i]);
            current[i] = make_pair(isa[i], i);
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= n; ++i)
        {
            read(isa[i]);
            current[i] = make_pair(isa[i], i);
        }
    }
    if (blocks <= 8)
    {
        for (int i = 1; i <= blocks; ++i)
        {
            head[i] = tail[i - 1] + 1;
            tail[i] = tail[i - 1] + each;
            if (i == blocks)     tail[i] = n;
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= blocks; ++i)
        {
            head[i] = tail[i - 1] + 1;
            tail[i] = tail[i - 1] + each;
            if (i == blocks)     tail[i] = n;
        }
    }
    if (blocks <= 8)
    {
        for (int i = 1; i <= blocks; ++i)
        {
            memcpy(block[i], block[i - 1], sizeof(block[0]));
            sort(current + head[i], current + 1 + tail[i]);
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                ++block[i][isa[j]];
                belong[j] = i;
                sorted[j] = current[j].first;
            }
            int satisfy = 0;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                FWT.Modify(isa[j], 1);
                satisfy += FWT.Query(n) - FWT.Query(isa[j]);
                previous[j] = satisfy;
            }
            intervals[i][i] = satisfy;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                suffix[j] = satisfy;
                FWT.Modify(isa[j], -1);
                satisfy -= FWT.Query(isa[j] - 1);
            }
        }
    }
    else
    {
        #pragma unroll 8
        for (int i = 1; i <= blocks; ++i)
        {
            memcpy(block[i], block[i - 1], sizeof(block[0]));
            sort(current + head[i], current + 1 + tail[i]);
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                ++block[i][isa[j]];
                belong[j] = i;
                sorted[j] = current[j].first;
            }
            int satisfy = 0;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                FWT.Modify(isa[j], 1);
                satisfy += FWT.Query(n) - FWT.Query(isa[j]);
                previous[j] = satisfy;
            }
            intervals[i][i] = satisfy;
            for (int j = head[i]; j <= tail[i]; ++j)
            {
                suffix[j] = satisfy;
                FWT.Modify(isa[j], -1);
                satisfy -= FWT.Query(isa[j] - 1);
            }
        }
    }
    if (blocks <= 8)
    {
        for (int dis = 1; dis <= blocks; ++dis)
        {
            for (int i = n - 1; i; --i)        block[dis][i] += block[dis][i + 1];
            for (int l = 1, r = dis + 1; r <= blocks + 1; ++l, ++r)
                intervals[l][r] = intervals[l + 1][r] + intervals[l][r - 1] - intervals[l + 1][r - 1] +
                                    Merge_Idx(head[l] - 1, head[r] - 1, tail[l] - head[l] + 1, tail[r] - head[r] + 1);
        }
    }
    else
    {
        #pragma unroll 8
        for (int dis = 1; dis <= blocks; ++dis)
        {
            for (int i = n - 1; i; --i)        block[dis][i] += block[dis][i + 1];
            for (int l = 1, r = dis + 1; r <= blocks + 1; ++l, ++r)
                intervals[l][r] = intervals[l + 1][r] + intervals[l][r - 1] - intervals[l + 1][r - 1] +
                                    Merge_Idx(head[l] - 1, head[r] - 1, tail[l] - head[l] + 1, tail[r] - head[r] + 1);
        }
    }

    if (m <= 8)
    {
        long long lastans = 0;
        for (int i = 0; i < m; ++i)
        {
            long long l, r;
            read(l, r);
            l ^= lastans;
            r ^= lastans;
            Behavior(l, r, lastans);
        }
    }
    else
    {
        long long lastans = 0;
        #pragma unroll 8
        for (int i = 0; i < m; ++i)
        {
            long long l, r;
            read(l, r);
            l ^= lastans;
            r ^= lastans;
            Behavior(l, r, lastans);
        }
    }
    return 0;
}

Description

Link.

无修支持查询:查询一个区间 $[l,r]$ 中所有子序列分别去重后的和 $\bmod\ p$

Solution

这是数据结构一百题的第50题(一半了哦)的纪念题解。

无修改操作,基本确定大方向莫队。

考虑查询的问题,我们可以转化一下。即求区间内每个数出现的次数。

一个区间 $[l,r]$ 的子序列数量为:

$$\sum_{i=0}^{r-l+1}C^{i}_{r-l+1}=2^{r-l+1}$$

比如一个数在区间 $[l,r]$ 出现了 $k$ 次,那么一共有 $2^{r-l+1-k}$ 个子序列不包含这个数。这个很好理解,从组合数的意义可知。那么就有 $2^{r-l+1}-2^{r-l+1-k}$ 个子序列包含了这个数。

那么我们就可以用莫队维护区间中出现了 $k$ 次的所有数的和,然后乘上一个 $2^{r-l+1}-2^{r-l+1-k}$ 就是它的贡献了。

问题又来了:每次询问的模数是不确定的,我们需要每次都需要 $\Theta(n)$ 处理一遍2的幂。

有没有什么方法能把处理这个东西的复杂度降到 $\Theta(\sqrt{n})$ 或以下呢?

对此SyadouHayami表示我们可以打个高精。

方法是有的。

我们可以每次询问都处理出 $2^{1},2^{2},\cdots,2^{\sqrt{n}}$ ,以及 $2^{2\sqrt{n}},2^{3\sqrt{n}},\cdots,2^{n}$,都只需要 $\Theta(\sqrt{n})$。当然,都是在模 $p$ 的意义下的。我们分别记为pow1pow2

那么 $2^{x}\operatorname{mod}p=(pow1_{x\operatorname{mod}\sqrt{n}}\times pow2_{\lfloor x\div\sqrt{n}\rfloor})\operatorname{mod}p$。

于是就解决问题了。

我的代码遇到了一下两个玄学问题,贴出来给同样情况的人看看:

  1. 链表部分的prevnext如果放在结构体里会T。
  2. pow1,pow2,sum,cnt几个数组的定义如果放在最开头和isa以及ans两个数组一起会RE。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int Maxn = 1e5 + 10;
const int Size = 320;
int n, m, isa[ Maxn ], ans[ Maxn ];
struct Query_Node
{
    int l, r, p, id, pos;
} Q[ Maxn ];
struct Linked_List
{
    int tot, prev[ Maxn ], next[ Maxn ];
    Linked_List( ) { tot = 0; }

    void insert( int x )
    {
        next[ tot ] = x;
        prev[ x ] = tot;
        tot = x;
    }

    void erase( int x )
    {
        if( tot == x )    tot = prev[ x ];
        else
        {
            next[ prev[ x ] ] = next[ x ];
            prev[ next[ x ] ] = prev[ x ];
        }
        prev[ x ] = next[ x ] = 0;
    }
} llt;

bool cmp( Query_Node rhs, Query_Node shr )
{
    if( rhs.pos != shr.pos )    return rhs.l < shr.l;
    else    return rhs.r < shr.r;
}

int pow1[ Maxn ], pow2[ Maxn ];
void Pare_Two( int p )
{
    pow1[ 0 ] = pow2[ 0 ] = 1;
    for( int i = 1; i <= Size; ++ i )     pow1[ i ] = 1ll * 2 * pow1[ i - 1 ] % p;
    for( int i = 1; i <= Size; ++ i )    pow2[ i ] = 1ll * pow1[ Size ] * pow2[ i - 1 ] % p;
}

int Get_Two( int x, int p )
{
    return 1ll * pow1[ x % Size ] * pow2[ x / Size ] % p;
}

int sum[ Maxn ], cnt[ Maxn ];
void Make_Cont( int x, int f )
{
    int pos = isa[ x ];
    sum[ cnt[ pos ] ] -= pos;
    if ( ! sum[ cnt[ pos ] ] ) llt.erase( cnt[ pos ] );
    if( f == 1 ) ++cnt[ pos ];
    else --cnt[ pos ];
    if ( ! sum[ cnt[ pos ] ] ) llt.insert( cnt[ pos ] );
    sum[ cnt[ pos ] ] += pos;
}

void Contribute( )
{
    int l = 1, r = 0;
    for( int i = 1; i <= m; ++ i )
    {
        Pare_Two( Q[ i ].p );
        while( l > Q[ i ].l ) Make_Cont( --l, 1 );
        while( l < Q[ i ].l ) Make_Cont( l++, 0 );
        while( r > Q[ i ].r ) Make_Cont( r--, 0 );
        while( r < Q[ i ].r ) Make_Cont( ++r, 1 );
        for( int s = llt.next[ 0 ]; s; s = llt.next[ s ] )
        {
            int val = 1ll * sum[ s ] * ( Get_Two( r - l + 1, Q[ i ].p ) - Get_Two( r - l + 1 - s, Q[ i ].p ) + Q[ i ].p ) % Q[ i ].p;
            ans[ Q[ i ].id ] += val;
            ans[ Q[ i ].id ] %= Q[ i ].p;
        }
    }
}

signed main( )
{
    scanf( "%d %d", &n, &m );
    for( int i = 1; i <= n; ++ i )    scanf( "%d", &isa[ i ] );
    for( int i = 1; i <= m; ++ i )
    {
        int l, r, p;
        scanf( "%d %d %d", &l, &r, &p );
        Q[ i ].l = l, Q[ i ].r = r;
        Q[ i ].p = p, Q[ i ].id = i;
        Q[ i ].pos = l / Size;
    }
    sort( Q + 1, Q + 1 + m, cmp );
    Contribute( );
    for( int i = 1; i <= m; ++ i )
        printf( "%d\n", ans[ i ] );
    return 0;
}

Description

(Link)[https://www.luogu.com.cn/problem/P4688].

每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。

Solution

读完题,我们可以发现每轮询问的答案是这个东西:

$$ \sum_{i=1}^{3}(r_{i}-l_{i}+1)-P\times 3 $$

其中,$P$ 表示三个区间里面的公共颜色数量。

前面那个 $\sum$ 里面的东西很好维护,我们主要来讲后面一部分的维护方法。

首先初始序列的 $a_{i}$ 是一定要离散化一遍的。

那么我们如何表示出出现的最少次数呢?

如果这是一个二进制串的话,我们可以发现这就是一个 $\operatorname{bit\_and}$ 的操作。

对于每个询问,我们可以把三个区间分开处理再合并。直接维护复杂度过高,用 bitset 优化。由于这是个二进制序列,所以离散化的时候不能去重,否则答案就会偏大。

直接操作容易 MLE,这里介绍一个小trick。我们可以把询问分批处理,这样就行了。

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

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

const int N = 1e5 + 5;
const int F = 23333 + 5;
int n, m, block, origin[N], appear[N];
int cnt[N], ans[N], vis[25005];
int l1[N], l2[N], l3[N];
int r1[N], r2[N], r3[N];
bitset < N > S[F];
vector < int > disc;
struct AskSet {
    int l, r;
    int id, p;
} Q[N];

int GetID(int x) {
    return lower_bound(disc.begin(), disc.end(), x) - disc.begin() + 1;
}

bool SortWith(const AskSet& x, const AskSet& y) {
    return (x.p < y.p) || (x.p == y.p && x.r < y.r);
}

void MakeCont(int pos, int flags, bitset < N >& bit) {
    pos = origin[pos];
    if (flags > 0) bit[pos + cnt[pos]] = 1;
    else bit[pos + cnt[pos] - 1] = 0;
    cnt[pos] += flags;
}

void Contribute(int fr, int ba) {
    memset(cnt, 0, sizeof cnt);
    memset(vis, 0, sizeof vis);
    bitset < N > bit; bit.reset();
    int l = 1, r = 0, subs = 0;
    for (int i = fr; i <= ba; ++i) {
        Q[++subs] = {l1[i], r1[i], i, appear[l1[i]]};
        Q[++subs] = {l2[i], r2[i], i, appear[l2[i]]};
        Q[++subs] = {l3[i], r3[i], i, appear[l3[i]]};
        ans[i] += (r3[i] - l3[i]) + (r2[i] - l2[i]) + (r1[i] - l1[i]) + 3;
    }
    sort(Q + 1, Q + 1 + subs, SortWith);
    for (int i = 1; i <= subs; ++i) {
        while (r < Q[i].r) MakeCont(++r, 1, bit);
        while (l > Q[i].l) MakeCont(--l, 1, bit);
        while (r > Q[i].r) MakeCont(r--, -1, bit);
        while (l < Q[i].l) MakeCont(l++, -1, bit);
        if (!vis[Q[i].id - fr + 1]) {
            vis[Q[i].id - fr + 1] = 1;
            S[Q[i].id - fr + 1] = bit;
        } else {
            S[Q[i].id - fr + 1] &= bit;
        }
    }
    for (int i = fr; i <= ba; ++i)
        ans[i] -= S[i - fr + 1].count() * 3;
}

signed main() {
    scanf("%d %d", &n, &m);
    block = sqrt(n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &origin[i]);
        appear[i] = (i - 1) / block + 1;
        disc.push_back(origin[i]);
    }
    sort(disc.begin(), disc.end());
    for (int i = 1; i <= n; ++i)
        origin[i] = GetID(origin[i]);
    for (int i = 1; i <= m; ++i) {
        scanf("%d %d", &l1[i], &r1[i]);
        scanf("%d %d", &l2[i], &r2[i]);
        scanf("%d %d", &l3[i], &r3[i]);
    }
    for (int i = 1; i <= m; i += 23333) Contribute(i, min(m, i + 23332));
    for (int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
    return 0;
}