[洛谷P4117][CF896E][Ynoi2018]五彩斑斓的世界

本文介绍了一种使用分块和并查集处理序列更新和查询的高效算法。通过将序列分为固定大小的块,利用并查集维护相同元素的位置和数量,实现区间元素更新和特定元素计数操作。关键在于优化整块操作,限制总减少量,并针对不同情况调整策略,确保时间复杂度可控。

【题目大意】

  • 给定一个长度为nnn的序列aaa,有mmm次操作:
  • (1).把区间[l,r][l,r][l,r]中大于vvv的数减去vvv
  • (2).查询区间[l,r][l,r][l,r]vvv的出现次数
  • 所有输入均在[1,100000][1,100000][1,100000]范围内

【算法分析】

  • 这种神仙题当然是分块
  • 如果只考虑整块操作,那么处于同一块且相同的数,每次要么一起减vvv要么一起不减
  • 那么考虑将同一块中相同的数放一起处理
  • 维护并查集
  • h[x][y]h[x][y]h[x][y]为块xxx中,数值yyy的第一次出现位置,没有出现则为000
  • val[find(x)]val[find(x)]val[find(x)]a[x]a[x]a[x]所在并查集的数值(find(x)find(x)find(x)xxx所在并查集的根,也就是h[h[h[xxx所在块][a[x]]][a[x]]][a[x]]
  • cnt[find(x)]cnt[find(x)]cnt[find(x)]表示这一块与a[x]a[x]a[x]相同大小的数有几个,即所在并查集的大小
  • 对于两边不完整的块的单点修改,修改后要重新计算整块的信息
  • 对于整块修改,记mx[x]mx[x]mx[x]为块xxx内的最大值
  • 发现mx[x]mx[x]mx[x]的总减少量最多100000100000100000
  • 那么考虑用O(v)O(v)O(v)的时间复杂度完成一次整块修改
  • 如果mx[x]&lt;=2vmx[x]&lt;=2vmx[x]<=2v,那么枚举需要减vvv的数值,最多vvv
  • 如果mx[x]&gt;2vmx[x]&gt;2vmx[x]>2v呢?
  • 又发现不论如何,小于等于vvv的数值总是不超过vvv
  • 因此可以将小于等于vvv的数加上vvv,然后整块减vvv
  • 需要打标记,记tag[x]tag[x]tag[x]为块xxx整块减去的值
  • 那么上面的分类讨论改为mx[x]−tag[x]mx[x]-tag[x]mx[x]tag[x]2v2v2v比较大小
  • 整块修改时也要修改hhhcntcntcntvalvalval
  • 发现mx[x]mx[x]mx[x]也需要修改
  • 如果mx[x]&gt;2vmx[x]&gt;2vmx[x]>2v,执行“将小于等于vvv的数加上vvv”后,mx[x]mx[x]mx[x]不变
  • 否则,mx[x]mx[x]mx[x]必定不增大,那么写一句while(!h[x][mx[x]])mx[x]−−;while(!h[x][mx[x]]) mx[x]--;while(!h[x][mx[x]])mx[x];就好了
  • 查询整块利用cnt[v][tag[x]+v]cnt[v][tag[x]+v]cnt[v][tag[x]+v]即可,注意tag[x]+vtag[x]+vtag[x]+v可能会越界

【参考程序】

#pragma GCC optimize(2)
#include <bits/stdc++.h>

using namespace std;

template <class t>
inline void read(t & res)
{
    char ch;
    while (ch = getchar(), !isdigit(ch));
    res = ch ^ 48;
    while (ch = getchar(), isdigit(ch))
    res = res * 10 + (ch ^ 48);
} 

const int e = 1e5 + 5, o = 320;
int h[o][e];
int bl[o], br[o], bel[e], f[e], cnt[e], n, m, val[e], mx[o], tag[o], a[e], s;

inline int find(int x)
{
    return f[x] == x ? x : f[x] = find(f[x]);
}

inline void merge(int x, int y)
{
    cnt[x] += cnt[y];
    f[y] = x;
}

inline void build(int x)
{
    int l = bl[x], r = br[x], i;
    mx[x] = 0;
    for (i = l; i <= r; i++) a[i] = val[find(i)];
    for (i = l; i <= r; i++)
    {
        f[i] = i;
        cnt[i] = 1;
        h[x][a[i]] = 0;
        mx[x] = max(mx[x], a[i]);	
    }
    for (i = l; i <= r; i++)
    {
        int &t = h[x][a[i]];
        if (!t) 
        {
            t = i;
            val[i] = a[i];
        }
        else merge(t, i);
    }
}

inline void upt(int l, int r, int v)
{
    int i, x = bel[l], frm = bl[x], to = br[x];
    for (i = frm; i <= to; i++) 
    {
        a[i] = val[find(i)];
        h[x][a[i]] = 0;
    }
    for (i = l; i <= r; i++)
    if (a[i] - tag[x] > v) a[i] -= v;
    for (i = frm; i <= to; i++)
    {
        val[i] = a[i];
        f[i] = i;
    }
    build(x);
}

inline void change(int x, int v)
{
    if (mx[x] - tag[x] <= v) return;
    int i, &t = tag[x], lim = min(mx[x], t + v);
    if (mx[x] - t >= 2 * v)
    {
        for (i = t + 1; i <= lim; i++)
        {
            int &y = h[x][i];
            if (!y) continue;
            int &u = h[x][i + v];
            if (!u) 
            {
                u = y;
                val[y] = i + v;
                y = 0;
            }
            else 
            {
                merge(u, y);
                y = 0;
            }
        }
        t += v;
    }
    else
    {
        for (i = t + v + 1; i <= mx[x]; i++)
        {
            int &y = h[x][i];
            if (!y) continue;
            int &u = h[x][i - v];
            if (!u)
            {
                u = y;
                val[y] = i - v;
                y = 0;
            }
            else
            {
                merge(u, y);
                y = 0;
            }
        }
        while (!h[x][mx[x]]) mx[x]--;
    }
}

inline void update(int l, int r, int v)
{
    int x = bel[l], y = bel[r];
    if (x == y)
    {
        upt(l, r, v);
        return;
    }
    upt(l, br[x], v);
    upt(bl[y], r, v);
    int i;
    for (i = x + 1; i < y; i++) change(i, v);
}

inline int ask(int l, int r, int v)
{
    int i, res = 0, x = bel[l];
    for (i = l; i <= r; i++)
    res += val[find(i)] - tag[x] == v;
    return res;
}

inline int qsum(int x, int v)
{
    int y = tag[x] + v;
    return y <= 1e5 ? cnt[h[x][y]] : 0;
}

inline int query(int l, int r, int v)
{ 
    int x = bel[l], y = bel[r];
    if (x == y) return ask(l, r, v);
    int i, res = ask(l, br[x], v) + ask(bl[y], r, v);
    for (i = x + 1; i < y; i++) res += qsum(i, v);
    return res;
}

int main()
{
    int i, j, tot = 0, opt, l, r, v;
    read(n); read(m);
    s = sqrt(n);
    for (i = 1; i <= n; i++) 
    {
        read(a[i]);
        f[i] = i;
        cnt[i] = 1;
        val[i] = a[i];
    }
    for (i = 1; i <= n; i = j + 1)
    {
        j = min(n, i + s - 1);
        bl[++tot] = i;
        br[tot] = j;
        for (int k = i; k <= j; k++) bel[k] = tot;
    }
    for (i = 1; i <= tot; i++) build(i);
    while (m--)
    {
        read(opt);
        read(l);
        read(r);
        read(v);
        if (opt == 1) update(l, r, v);
        else printf("%d\n", query(l, r, v));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值