JZOJ 3985 数组

数组

Description

给出一个长度为 n 的序列,每个位置上都有一种颜色。
我们称一个区间是合法的当且仅当该区间内没有两个颜色相同的位置。
接着给出m此操作,类型为以下两种中的一种:
1、修改:每次可以修改一个位置的颜色。
2、询问:询问整个序列内有多少个合法的子区间。

Data Constraint

n <=100000 m <=2* n ,颜色的编号范围为1~ n

Solution

第一次用线段树维护单调栈,猎奇操作。

lasti表示和 i 相同颜色的在i前面的第一个位置的编号,那么以 i 为右端点的合法的子区间个数为i- max ( lastj )( j <=i)。
那我们设 sufi = max ( lastj )( j <=i)
那么答案显然为 n(n+1)2 - ni=1sufi

关于 lasti 的实时维护,我们可以对每一种颜色开一个 set ,考虑到一个点颜色的变化最多只会影响到三个点的 last 值的变化,这些位置都可以在 set 中用 lower _ bound 快速找到。

接下来考虑如何求 sufi ,我们可以从左往右做同时维护一个单调栈,做到i时的栈顶高度就是 sufi

接下来考虑用线段树维护单调栈。
给出函数 query ( l ,r, high )表示在( l ,r)这段区间前前插入了一个大小为 high 的数,从 high 开始往右做 l ~r suf 的总和。
那显然有答案为 query ( 1 ,n, 0 )。
接着考虑如何求query,设区间( l ,mid)的最大值为 lx

倘若我们要求 query ( l ,r, high )。
1、若 lx <= high ,则返回( mid - l +1)* high + query ( mid + 1 ,r, high )
2、若 lx > high ,则返回 query ( l ,mid, high )+ query ( mid + 1 ,r, lx )

我们发现 query ( mid + 1 ,r, lx )与 high 并没有什么关系,所以可以提前预处理好。
由于每次修改会影响 log2 n 个节点,每一个节点都需要log2 n 的时间维护query( mid + 1 ,r, lx ),所以单次修改的复杂度为 O (log22 n )。
而执行query的时候每次区间长度至少少一半,故时间复杂度为 O (log2 n ),故总的复杂度为O( n log22 n )。

其实这种做法可以理解成把整个序列分成被high影响的和不被 high 影响的两部分,用线段树上的二分找到这两部分的边界从而计算出答案。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#pragma GCC optimize(3)

#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)

using namespace std;
typedef long long ll;
const ll N=12e4,M=4*N;

set<int> color[N];
int last[N],used[N],ys[N];
ll keep[M],zd[M];
int x,y,n,q,i,j,k,l,o;

inline ll max(ll a,ll b)
{return a>b?a:b;}

ll query(int o,int l,int r,ll op)
{
    if(l==r)return max(op,last[l]);
    int mid=l+r>>1;
    if(zd[o*2]<=op)return (mid-l+1)*op+query(o*2+1,mid+1,r,op);
    else return query(o*2,l,mid,op)+keep[o*2+1];
}

void build(int o,int l,int r)
{
    if(l==r){
        zd[o]=last[l];
        return;
    }
    int mid=l+r>>1;
    build(o*2,l,mid); build(o*2+1,mid+1,r);
    zd[o]=max(zd[o*2],zd[o*2+1]);
    keep[o*2+1]=query(o*2+1,mid+1,r,zd[o*2]);
}

void change(int o,int l,int r,int op,int yu)
{
    if(l==r){
        last[l]=zd[o]=yu;
        return ;
    }
    int mid=l+r>>1;
    if(op<=mid)change(o*2,l,mid,op,yu);
    else change(o*2+1,mid+1,r,op,yu);
    zd[o]=max(zd[o*2],zd[o*2+1]);
    keep[o*2+1]=query(o*2+1,mid+1,r,zd[o*2]);
}

int main()
{
    cin>>n;
    fo(i,1,n)color[i].clear();
    fo(i,1,n)color[i].insert(0),color[i].insert(n+1);
    fo(i,1,n){
        scanf("%d",&x); ys[i]=x; 
        color[x].insert(i);
        last[i]=used[x]; used[x]=i;
    }
    build(1,1,n);
    cin>>q; ll ans=(ll)n*(n+1)>>1;
    fo(i,1,q){
        scanf("%d",&x);
        if(x==0)printf("%lld\n",ans-query(1,1,n,0));
        else{
            scanf("%d%d",&x,&y);
            if(ys[x]==y)continue;
            int wz=*color[ys[x]].lower_bound(x+1);
            if(wz<=n){
                int w1=*(--color[ys[x]].lower_bound(x));
                change(1,1,n,wz,w1);
            }
            color[ys[x]].erase(x);
            ys[x]=y;
            int w2=*(--color[y].lower_bound(x));
            int w3=*color[y].lower_bound(x);
            color[y].insert(x);
            change(1,1,n,x,w2);
            if(w3!=n+1)change(1,1,n,w3,x);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值