Codeforces 940F

32 篇文章 0 订阅
5 篇文章 0 订阅

题目链接:http://codeforces.com/contest/940/problem/F

CF Round #466的最后一题,颇有难度,正解是带修改莫队算法。

【题意】

给定一个长度为nn的数组aa,并且要求执行qq个操作,有两种不同的操作:

①询问一个区间[l,r][l,r]中集合{c0,c1,c2,,c109}{c0,c1,c2,⋯,c109}Mex,而cici表示数值ii[l,r][l,r]中的出现次数。

②把apap修改成xx

对每一个询问输出答案。

【题解】

典型的区间问题,不要求在线,可以考虑莫队。

有时间轴影响,故使用带修改莫队,时间复杂度应为O(n53)O(n53)

先对区间的移动进行分析:

使用离散化技巧,把输入数据压缩至n+qn+q的范围内。

维护两个数组count1,count2count1,count2count1count1记录(离散后的)每个数的出现次数,count2count2记录count1count1中的数的出现次数。

那么所求为count2count2中第一个为0的下标位置。

对于count1,count2count1,count2,我们都可以O(1)O(1)维护每个操作对数组的影响,接下来考虑如何计算答案。

count2count2数组的变动,让第一个为0的下标位置可能会有很大的跳跃,不好维护,那么我们注意到一个性质:

答案不会超过O(n)O(n),为什么呢?

假如要将count21,count22,,count2kcount21,count22,⋯,count2k填满的话,至少需要k(k+1)2k(k+1)2个元素,可是数组的总长只有nn,所以答案必然不能太大。

那么有了这个性质,可以暴力维护答案,维护答案的总的复杂度不会超过O(qn)O(qn)

关于莫队,还有几个需要注意的地方:

第一个是当维护区间变化时,先考虑"伸展",再考虑"压缩",要不然会出现区间r<lr<l的情况。

一般的莫队不会太在意这个,因为后面会再加回来,但是这题中可能会导致中间结果多减了,导致count2count2数组越界。

第二个是在带修改莫队时间轴移动上,千万不要颠倒了时间顺序,这其实也是常识了,不过我被这个卡了一会儿。


#include <bits/stdc++.h>

using namespace std;


const int N = 2E5 + 7;

map<int,int>mp;
int cnt[N], vis[N], a[N];
int BLOCK;
int ans[N];
int num[N], tot;
int n, q;
int cntq ;
int cntc ;


struct QQ
{
    int l, r, z, id;
}Q[N];
struct CC
{
    int x, y;
}C[N];
bool cmp(QQ a, QQ b)
{
    if(a.l / BLOCK == b.l / BLOCK) {
        if(a.r / BLOCK == b.r / BLOCK) return a.z < b.z;
        return a.r < b.r;
    } else {
        return a.l < b.l;
    }
}

void add(int x)
{
    --vis[cnt[num[x]]];
    ++cnt[num[x]];
    ++vis[cnt[num[x]]];
}
void dec(int x)
{
    --vis[cnt[num[x]]];
    --cnt[num[x]];
    ++vis[cnt[num[x]]];
}


void solve()
{
    int l = 1, r = 1, c = 0, res;
    add(1);
    for(int i = 1;i <= cntq; ++ i) {
        while(r < Q[i].r) add(++r);
        while(r > Q[i].r) dec(r--);
        while(l < Q[i].l) dec(l++);
        while(l > Q[i].l) add(--l);

        while(c > Q[i].z){
            if(C[c].x >= l && C[c].x <= r)    dec(C[c].x);
            swap(C[c].y, num[C[c].x]); //重点
            if(C[c].x >= l && C[c].x <= r)    add(C[c].x);
            c--;
        }
        while(c < Q[i].z){
            c++;
            if(C[c].x >= l && C[c].x <= r)    dec(C[c].x);
            swap(C[c].y, num[C[c].x]);
            if(C[c].x >= l && C[c].x <= r)    add(C[c].x);
        }
        res = 1;
        while(vis[res]) ++res;
        ans[Q[i].id] = res;
    }
    for(int i = 1;i <= cntq;i ++) printf("%d\n",ans[i]);
}

int main()
{
    scanf("%d %d", &n, &q);
    BLOCK = (int)pow(n, 2.0/3.0);
    for(int i = 1;i <= n; ++ i) {
        int x;
        scanf("%d", &x);
        if(mp[x]) num[i] = mp[x];
        else num[i] = mp[x] = ++tot;
    }
    int opt, l, r;
    for(int i = 1;i <= q; ++ i) {
        scanf("%d %d %d", &opt, &l, &r);
        if(opt == 1) {
            ++ cntq;
            Q[cntq] = (QQ){l,r,cntc,cntq};
        } else {
            ++ cntc;
            if(mp[r]) r = mp[r];
            else r = mp[r] = ++tot;
            C[cntc] = (CC){l, r};
        }
    }
    sort(Q+1,Q+1+cntq,cmp);
    solve();
    return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值