BZOJ 2120 数颜色 (分块+二分)

题意
给你一个长度为n的数组,每个数字代表一种颜色,有两种操作:
1 问你在区间L,R中有多少种颜色,
2 把第i个位置的颜色变为C
思路:
记录一个数组pre[i]表示i这个位置的颜色往左推到的最近的相同颜色的位置是哪里,那么对于一次查询来说,如果pre[i] < L 就表示离他最近的颜色不再L区间里,之后我们可以分块儿,对于块内元素排序二分找,块外元素暴力去找不理解的看
https://blog.csdn.net/wjmwsgj/article/details/81176804 第二题

代码

#pragma GCC optimize("O3")
#pragma G++ optimize("O3"
#include <bits/stdc++.h>
using namespace std;

const int maxn = 10000 + 10;
int v[maxn] , pos[maxn]  , sz , n , m , b[maxn] ,pre[maxn];
int last[1000001];
void reset(int x)
{
    int l = (x - 1) * sz + 1, r = min(sz * x, n);
    for (int i = l; i <= r;i++) pre[i] = b[i];
    sort(pre+l,pre+r+1);
}
int find(int x,int v)
{
    int l=(x-1)*sz+1,r=min(x*sz,n);
    int first=l;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(pre[mid]<v)l=mid+1;
        else r=mid-1;
    }
    return l-first;
}
void build()
{
    for (int i = 1; i <= n;i++)
    {
        pos[i] = (i - 1) / sz + 1;
        b[i] = last[v[i]];
        last[v[i]] = i;
    }
    for (int i = 1; i <= pos[n]; i++) reset(i);
}

 int query(int l,int r)
 {
     int ans = 0;
     for(int i = l ; i <= min(r,pos[l]*sz) ; i++) if(b[i] < l) ans++;
     if(pos[l] != pos[r])
     {
         for(int i = (pos[r] - 1) * sz + 1 ; i <= r ; i++) if(b[i] < l) ans ++;
     }
     for (int i = pos[l] + 1; i <= pos[r] - 1;i++) ans += find(i, l);
     return ans;
 }
void update(int p,int c)
{
    for (int i = 1; i <= n;i++) last[v[i]] = 0;
    v[p] = c;
    for (int i = 1; i <= n;i++)
    {
        int t = b[i];
        b[i] = last[v[i]];
        //for(int j = 1 ; j <= n ; j++) printf("%d %d\n",last[v[j]] ,b[j]);puts("~~~~`");
        if (t != b[i]) reset(pos[i]);
        last[v[i]] = i;
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    {
        sz = sqrt(n);
        for (int i = 1; i <= n;i++) scanf("%d", &v[i]);
        build();
        char sh[10];
        int x, y;
        for (int i = 1; i <= m;i++)
        {
            scanf("%s%d%d",sh, &x, &y);
            if(sh[0] == 'Q') printf("%d\n", query(x, y));
            else update(x, y);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值