莫队算法——解决序列上询问的利器 (2) 带修改的莫队

30 篇文章 0 订阅

  普通的莫队戳这里
  还是考虑类似的问题:有一个长为N序列,有M个操作:1.询问:在区间[L,R]内,出现了多少个不同的数字。2.修改,将第x个数改为v(序列中所有数字均小于K)。题目会给出K。

  做法其实是类似的,只是要考虑更新的问题。
  有一种值得思考的做法:首先离线下所有的修改操作。对于某一次询问[L,R], 先假设没有任何修改,算出答案来。再考虑所有在这次询问之前的修改操作,如果修改的数在这个询问的区间内,则更新答案。
  
  我们仍然按照以前的方法将所有询问离线后排序,只是要记录在它之前一共经历了多少次修改操作。由于没有必要对于每一次询问都从第1次修改开始修改数组,我们可以再加入一个curT(time),代表当前完成了多少次修改,每一次询问就像curL和curR的移动一样移动curT就行了。

  例题:BZOJ2120 数颜色
  Description
    墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令: 1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。 2、 R P Col 把第P支画笔替换为颜色Col。为了满足墨墨的要求,你知道你需要干什么了吗?
  Input
    第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。
  Output
    对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。
  Sample Input
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
  Sample Output
4
4
3
4
  HINT
    对于100%的数据,N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6。

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

using namespace std ;

bool Read ( int &x ) { char c = getchar() ; x = 0 ; bool f = 0 ; while ( !isdigit(c) ) { if ( c == '-' ) f = 1 ; if ( c == EOF ) return false ; c = getchar() ; } while ( isdigit(c) ) { x = 10 * x + c - '0' ; c = getchar() ; } if (f) x = -x ;return true ; }
void Print ( int x ) { int len = 0, a[50] ; if ( x == 0 ) { putchar('0') ; return ; } if ( x < 0 ) { putchar('-') ; x = -x ; } while (x) { a[++len] = x%10 ; x /= 10 ; } while (len) putchar(a[len--]+'0') ;}
const int maxn = 1e5+10, maxm = 1e6 ;
int n, m, a[maxn], cnt[maxm], answer, num, tot, curL, curR, curT, block ;

struct Query {
    int L, R, lst, id, ans ;
    friend bool operator < ( Query a, Query b ) {
        return (a.L/block==b.L/block) ? a.R < b.R : a.L < b.L ;
    }
} q[maxn] ;
bool cmp ( Query a, Query b ) {
    return a.id < b.id ;
}
struct Update {
    int x, vnew, vold ;
} u[maxn] ;

void add ( int pos ) {
    if ( ++cnt[ a[pos] ] == 1 ) ++ answer ;
}

void remove ( int pos ) {
    if ( --cnt[ a[pos] ] == 0 ) -- answer ;
}

void add_update ( int wea ) {
    u[wea].vold = u[wea].x[a] ;
    if ( u[wea].x >= curL && u[wea].x <= curR ) remove(u[wea].x) ;
    u[wea].x[a] = u[wea].vnew ;
    if ( u[wea].x >= curL && u[wea].x <= curR ) add(u[wea].x) ;
}

void remove_update ( int wea ) {
    u[wea].vnew = u[wea].x[a] ;
    if ( u[wea].x >= curL && u[wea].x <= curR ) remove(u[wea].x) ;
    u[wea].x[a] = u[wea].vold ;
    if ( u[wea].x >= curL && u[wea].x <= curR ) add(u[wea].x) ;
}

char cmd[5] ;
int main() {
    Read(n) ; Read(m) ;
    block = floor( sqrt(n)+0.5 ) ;
    curL = 1 ;
    curR = 0 ;
    curT = 0 ;
    int i, j, k, l, r ;
    for ( i = 1 ; i <= n ; i ++ )
        Read(a[i]) ;
    while (m--) {
        scanf ( "%s", cmd ) ;
        if ( cmd[0] == 'Q' ) {
            Read(l) ; Read(r) ;
            q[++tot] = (Query) { l, r, num, tot } ;
        } else { 
            Read(l) ; Read(k) ;
            u[++num] = (Update){ l, k, 0 } ;
        }
    }
    sort ( q+1, q+tot+1 ) ;

    for ( i = 1 ; i <= tot ; i ++ ) {
        while ( curL < q[i].L ) 
            remove(curL++) ;
        while ( curL > q[i].L ) 
            add(--curL) ;
        while ( curR < q[i].R ) 
            add(++curR) ;
        while ( curR > q[i].R ) 
            remove(curR--) ;
        while ( curT < q[i].lst ) 
            add_update(++curT) ;
        while ( curT > q[i].lst ) 
            remove_update(curT--) ;
        q[i].ans = answer ;
    }
    sort ( q+1, q+tot+1, cmp ) ;
    for ( i = 1 ; i <= tot ; i ++, putchar('\n') ) 
        Print(q[i].ans) ;

    return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值