AcWing 2521:数颜色 ← 带修改的莫队算法

【题目来源】
https://www.acwing.com/problem/content/2523/

【题目描述】
墨墨购买了一套 N 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。
墨墨会像你发布如下指令:
    Q L R:代表询问你从第 L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。
    R P Col:把第 P 支画笔替换为颜色 Col。
为了满足墨墨的要求,你知道你需要干什么了吗?

【输入格式】
第 1 行两个整数 N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第 2 行 N 个整数,分别代表初始画笔排中第 i 支画笔的颜色。
第 3 行到第 2+M 行,每行分别代表墨墨会做的一件事情,格式见题干部分。

【输出格式】
对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。

【数据范围】
1≤N,M≤10000,
修改操作不多于 1000 次,
所有的输入数据中出现的所有整数均大于等于 1 且不超过 10^6。

【输入样例】
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6

【输出样例】
4
4
3
4

【算法分析】
● 基础莫队算法详见:
https://blog.csdn.net/hnjzsyjyj/article/details/138976338  

● 基础莫队算法是“
离线+暴力+分块”算法,通常用于“不修改、只查询”的区间问题。
在线”是交互式的,一问一答。特别的,如果前面的答案用于后面的提问,称为“强制在线”。
离线”是非交互的,一次性读取所有问题,一起回答
如果只是涉及简单的“
单点修改”的区间问题,也可以使用莫队算法。这便是带修改的莫队算法

● 在带修改的莫队算法中,一个查询通常表示为
(L,R,t),含义为在查询区间 [L,R] 前进行了 t 次修改操作。一般地,可以把 t 理解为“时间”。t 的范围为 1≤t≤m,m为操作(查询、修改)次数。两个查询的 t 相差越小,它们对应的数据差别越小,计算量也越小。所以,对 t 排序能减少计算量。

● 在带修改的莫队算法中,块的大小为 block=n^{\frac{2}{3}},而不是基础莫队算法的 sqrt(n) 个块。其中,n 为元素个数。

莫队分块思想详见:https://blog.csdn.net/hnjzsyjyj/article/details/138955263

● 莫队算法的精髓在于奇偶性排序。因此,带修改的莫队算法依然会进行奇偶性排序。也就是依据左端点 L 位于奇数块还是偶数块,决定右端点 R 从小到大排序还是从大到小排序。
(1)首先,利用分块算法将给定的 n 个数分成 n^{\frac{2}{3}} 块,而不是基础莫队算法的 sqrt(n) 个块;
(2)然后,将多个询问的
左端点 L 按块从小到大排序
若 L 位于
奇数块,则对右端点 R 从小到大排序。若 L 位于偶数块,则对右端点 R 从大到小排序。反之亦可。
示意图如下所示:

​​​​​
【算法代码】

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e4+5;
const int maxs=1e6+5;

int a[maxn],id[maxn],ans[maxn];
int cnt[maxs];
int qt,mt; //query times, modify times
int num;
int n,m;

struct Query {
    int sn,x,y,t; //The order is the same as line 59
} q[maxn];

struct Change {
    int p,v; //position,val
} md[maxn];

bool cmp(Query a,Query b) {
    if(id[a.x]!=id[b.x]) return id[a.x]<id[b.x];
    if(id[a.y]!=id[b.y]) return id[a.y]<id[b.y];
    return a.t<b.t;
}

void add(int x) {
    cnt[x]++;
    if(cnt[x]==1) num++;
}

void del(int x) {
    cnt[x]--;
    if(!cnt[x]) num--;
}

void mod(int t,int i) {
    if(q[i].x<=md[t].p && md[t].p<=q[i].y) {
        add(md[t].v);
        del(a[md[t].p]);
    }
    swap(a[md[t].p],md[t].v);
}

int main() {
    scanf("%d%d",&n,&m);
    int block=pow(n,2.0/3.0);
    for(int i=1; i<=n; i++) {
        scanf("%d",&a[i]);
        id[i]=(i-1)/block+1;
    }

    char ch;
    int x,y;
    for(int i=1; i<=m; i++) {
        getchar();
        scanf("%c",&ch);
        scanf("%d%d",&x,&y);
        if(ch=='Q') q[++qt]= {qt,x,y,mt};
        else md[++mt]= {x,y};
    }

    sort(q+1,q+qt+1,cmp);

    int L=1,R=0,t=0;
    for(int i=1; i<=qt; i++) {
        while(L<q[i].x) del(a[L++]);
        while(L>q[i].x) add(a[--L]);
        while(R<q[i].y) add(a[++R]);
        while(R>q[i].y) del(a[R--]);
        while(t<q[i].t) mod(++t,i);
        while(t>q[i].t) mod(t--,i);
        ans[q[i].sn]=num;
    }

    for(int i=1; i<=qt; i++) printf("%d\n",ans[i]);

    return 0;
}


/*
in:
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6

out:
4
4
3
4
*/




【参考文献】
https://blog.csdn.net/weixin_75161465/article/details/137195888
https://blog.csdn.net/GROZAX/article/details/130069889
http://fangkaipeng.com/?p=1504
https://www.acwing.com/solution/content/157290/





 

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值