【2016北京集训】数组

Portal --> broken qwq

Description

​  给你一个数组,每个元素有一个颜色,要求支持两种操作:

1、修改某个元素的颜色

2、询问这个数组有多少个自取件内没有重复的颜色

​​  数据范围:\(n<=10^5,m<=2n\),颜色大小在\(1\sim n\)之间

​  

Solution

​​  这题。。本来应该是一个树套树题

​​  但是为什么一定要用树套树呢对吧qwq

​​  首先是套路:考虑维护一个\(pre\)数组,表示每个节点的前一个最近的和它颜色一样的节点,那么我们考虑固定一个右端点,将左端点往左移,直到再往前移一位就会导致当前区间内的\(pre\)的最大值在这个区间内,这个时候区间的长度就是这个右端点对答案的贡献

​  然而。。直接这样做是不行的因为我们要支持修改和多组询问qwq

​  所以这里考虑用线段树维护一个神秘的东西:对于线段树中的一个节点,假设它对应的区间是\([l,r]\),那么我们维护一个\(mx\)\(ans\),分别表示当前区间内\(pre\)的最大值,以及,只考虑当前区间的\(pre\)限制的右端点贡献之和(这个概念描述起来有点神秘,具体一点就是:首先这个\(ans\)记录的是该区间中的每个点作为右端点算得的贡献之和,但是这个贡献在计算的时候,只考虑当前区间内的\(pre\)的影响,更加直观一点来说就是可以理解为移动左端点求\(pre\)的最大值的时候,如果说新加进来的位置不在\([l,r]\)区间内,就不取\(max\)),然后答案就应该是线段树根节点的\(ans\)值了,对于叶子节点来说\(ans=x-pre[x]\),其中\(x\)是这个叶子节点在数组中对应的位置

​  现在考虑怎么维护这个东西

​  为了方便接下来的描述,约定用\(x\)表示当前区间,\(L\)表示当前区间的左儿子(左半部分),\(R\)表示当前区间的右儿子(右半部分),然后我们考虑怎么用\(ans[L]\)\(ans[R]\)求得\(ans[x]\),这里需要根据\(mx[L]\)\(mx[R]\)的大小关系进行一些讨论,首先我们先看最简单的情况:

(1)如果说\(mx[L]>=mx[R]\):首先\(ans[L]\)肯定还是会作为\(ans[x]\)的一部分的,因为加入的东西在后面,不会影响\(ans[L]\)的贡献,然后我们看\([mid+1,r]\)区间中的元素,因为\(mx[R]<=mx[L]\),也就是说后面的元素作为右端点的时候左端点停下的地方肯定在\(mx[L]+1\)这个位置,所以我们可以直接计算贡献:\(ans[x]=ans[L]+(mid-mx[L])*(r-mid)+\sum\limits_{i=1}^{r-mid}i\),具体一点的话就是\([mid+1,r]\)中的每个元素对应的左端点可以先走到\(mid+1\)这个位置,再走到\(mx[L]+1\)的位置

​  

​​  接下来看复杂一点的另一种情况:

(2)如果说\(mx[L]<mx[R]\),那么说明\([mid+1,r]\)中有一部分的元素对应的左端点可以走到\(mx[L]+1\),有的在\([mid+1,r]\)中的某个位置就停下了,这个时候我们就不能直接计算答案了,由于\(pre\)\(max\)值在左端点的移动过程中是递增的,所以我们可以考虑递归求解,在递归求解的时候也是通过这个右儿子左儿子的\(mx\)值和\(mx[L]\)进行比较,如果可以直接计算答案就直接计算,否则继续递归,遇到叶子的话也是返回,因为只有一个节点了可以直接计算

​  

​  最后就是\(pre\)的修改,我们只要对每个值用一个\(set\)随便维护一下下标位置就好啦

​  

​​  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#define ll long long
using namespace std;
const int N=1e5+10,SEG=N*4;
int pre[N],a[N];
set<int> rec[N];
set<int>::iterator it,fr,nxt;
int n,m;
ll sum(int l,int r){return 1LL*(l+r)*(r-l+1)/2;}
namespace Seg{/*{{{*/
    int ch[SEG][2],mx[SEG];
    ll ans[SEG];
    int n,tot;
    ll calc(int x,int l,int r,int L){
        int mid=l+r>>1;
        if (l==r) return l-max(mx[L],mx[x]);
        if (mx[ch[x][0]]<=mx[L])
            return 1LL*(l-1-mx[L])*(mid-l+1)+sum(1,mid-l+1)+calc(ch[x][1],mid+1,r,L);
        else
            return (ans[x]-ans[ch[x][0]])+calc(ch[x][0],l,mid,L);
    }
    void pushup(int x,int l,int r){
        int mid=l+r>>1;
        mx[x]=max(mx[ch[x][0]],mx[ch[x][1]]);
        ans[x]=ans[ch[x][0]];
        if (mx[ch[x][0]]>=mx[ch[x][1]])
            ans[x]+=1LL*(mid-mx[ch[x][0]])*(r-mid)+sum(1,r-mid);
        else
            ans[x]+=calc(ch[x][1],mid+1,r,ch[x][0]);
    }
    void _build(int x,int l,int r){
        mx[x]=0;
        if (l==r){
            mx[x]=pre[l]; ans[x]=l-pre[l];
            return;
        }
        int mid=l+r>>1;
        ch[x][0]=++tot; _build(ch[x][0],l,mid);
        ch[x][1]=++tot; _build(ch[x][1],mid+1,r);
        pushup(x,l,r);
    }
    void build(int _n){n=_n; tot=1; _build(1,1,n);}
    void _update(int x,int d,int lx,int rx){
        if (lx==rx){
            mx[x]=pre[lx]; ans[x]=lx-pre[lx];
            return;
        }
        int mid=lx+rx>>1;
        if (d<=mid) _update(ch[x][0],d,lx,mid);
        else _update(ch[x][1],d,mid+1,rx);
        pushup(x,lx,rx);
    }
    void update(int d){_update(1,d,1,n);}
    void debug(int x,int l,int r){
        if (l==r){printf("%lld ",ans[x]); return;}
        int mid=l+r>>1;
        debug(ch[x][0],l,mid);
        debug(ch[x][1],mid+1,r);
    }
    void debug(){debug(1,1,n);}
}/*}}}*/
void update(int x,int delta){
    it=rec[a[x]].find(x);
    fr=it; nxt=it; --fr; ++nxt;
    if (nxt!=rec[a[x]].end()){
        pre[*nxt]=*fr;
        Seg::update(*nxt);
    }
    rec[a[x]].erase(x); 
    a[x]=delta;
    rec[a[x]].insert(x);

    it=rec[a[x]].find(x);
    fr=it; nxt=it; --fr; ++nxt;
    if (nxt!=rec[a[x]].end()){
        pre[*nxt]=x;
        Seg::update(*nxt);
    }
    pre[x]=*fr;
    Seg::update(x);
}
void debug(){
    Seg::debug();
    printf("\n");
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    int op,x,to;
    scanf("%d",&n);
    for (int i=1;i<=n;++i) rec[i].insert(0);
    for (int i=1;i<=n;++i){
        scanf("%d",a+i);
        pre[i]=*(--rec[a[i]].end());
        rec[a[i]].insert(i);
    }
    Seg::build(n);
    scanf("%d",&m);
    for (int i=1;i<=m;++i){
        scanf("%d",&op);
        if (op==0) 
            printf("%lld\n",Seg::ans[1]);
        else{
            scanf("%d%d",&x,&to);
            update(x,to);
        }
        //debug();
    }
}

转载于:https://www.cnblogs.com/yoyoball/p/9721182.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值