2018.8.7T2(线段树)

描述
YJC最近在研究数组,他认为如果数组的一个区间内不包含重复的元素,那么这个区间是一个优美的区间。

现在YJC弄到了一个interesting的数组,他想知道这个数组有多少个优美的区间。当然,他有时候也会觉得这个数组不够interesting,此时他会修改数组中的一个元素。

YJC发现在修改之后他不会算有多少个优美的区间了,于是他来向你求助。

输入格式
第一行输入一个数n,表示数组长度。

接下来一行输入n个整数,第i个整数ai是数组中的第ii个元素。

接下来一行输入一个数q,表示操作数量。

接下来q行每行表示一次操作。如果某行第一个数为0,则表示一次询问。否则后面输入两个数x和y,表示将a[x]修改为y。

输出格式
对于每次询问,输出一行一个整数表示答案。

样例1
样例输入
3
1 2 3
3
0
1 1 2
0
样例输出
6
4


和标算无关的暴力就不说了
我们考虑一个正确性显然的事情
我们记 next[i] n e x t [ i ] a[i] a [ i ] 左边第一个相等的位置
那么对于左端点 i i 来说,他可能的右端点最大值为min{ next[j] n e x t [ j ] } (j>=i) ( j >= i )
那么我们就得到一个 O(nm) O ( n m ) 的暴力。
考虑怎么优化
这个 next n e x t 数组的更新可以通过 set s e t 做到 logn l o g n ,那么现在另一个瓶颈就是答案的计算
考虑用线段树维护。
维护一个类似区间和的东西(实际上不是区间和)
我们是用线段树维护一下每个点的 next n e x t 以及一下几个变量:
mn[root] m n [ r o o t ] ,区间内 next n e x t 的最小值
sum[root] s u m [ r o o t ] ,区间内除了最小值左边的位以外每一位的 min m i n { next[j] n e x t [ j ] } (j>=i) ( j >= i )
det[root] d e t [ r o o t ] ,维护 sum[root]sum[ls]sum[rs] s u m [ r o o t ] − s u m [ l s ] − s u m [ r s ]
这些变量在我们转移以及计算答案时都要用到,缺一不可
具体转移见代码

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define ll long long
#define ls root * 2
#define rs root * 2 + 1
const int maxn = 101000;
int n , m;
int a[maxn];
int nextt[maxn] , last[maxn];
ll mn[4*maxn] , delt[4*maxn] , sum[4*maxn];
ll p[4*maxn];
ll pl , tmp , pos_mn;

set<int>in[maxn];
set<int>::iterator it;
int read()
{
    int sum = 0;char c = getchar();bool flag = true;
    while( c < '0' || c > '9' ) {if(c == '-') flag = false;c = getchar();}
    while( c >= '0' && c <= '9' ) sum = sum * 10 + c - 48 , c = getchar();
    if(flag)  return sum;
     else return -sum;
}  
void find(int root,int l,int r)
{
    int mid = (l + r)/2;
    if(l == r)
    {
        pl = l;
        return;
    }
    if(p[mn[rs]] < pos_mn) find(rs,mid+1,r);
    else
    {
        tmp += sum[rs] + delt[root];
        find(ls,l,mid);
    }
    return;
}
void update(int root,int l,int r)
{
    if(p[mn[rs]] < p[mn[ls]])
    {
        mn[root] = mn[rs];
        sum[root] = sum[rs];
        return; 
    }
    int mid = (l+r)/2;
    mn[root] = mn[ls];
    tmp = pl = 0;
    pos_mn = p[mn[rs]];
    find(ls,l,mid);
    sum[root] = sum[ls] + sum[rs] - tmp + (p[mn[rs]] * (mn[rs] - pl) );
    delt[root] = sum[root] - sum[ls] - sum[rs];
    return;
}
void build(int root,int l,int r)
{
    if(l == r)
    {
        mn[root] = l;sum[root] = 0;
        return;
    }
    int mid = (l + r)/2;
    build(ls,l,mid);build(rs,mid+1,r);
    update(root , l , r);
}
void init()
{ 
    n = read();
    rep(i,1,n)
    {
        a[i] = read();
        in[a[i]].insert(i);
        last[i] = p[a[i]];
        if(last[i]) nextt[last[i]] = i;
        p[a[i]] = i;
    }
    rep(i,1,n)
    {
        if(!nextt[i]) nextt[i] = n + 1;
        p[i] = nextt[i];
    }
    build(1,1,n);
    return;
}
void New(int root,int l,int r,int pl)
{
    if(l == r) return;
    int mid = (l + r)/2;
    if(mid >= pl) New(ls,l,mid,pl);
    else New(rs,mid+1,r,pl);
    update(root,l,r);
    return;
}
void change(int x,int v)
{
    p[x] = v;
    New(1,1,n,x);
    return;
}
int main()
{
    init();
    m = read();
    rep(i,1,m)
    {
        int k = read();
        if(k == 0)
            printf("%lld\n",sum[1] + p[mn[1]] * mn[1] - 1ll*n*(n+1)/2);
        else
        {
            int pl = read() , x = read();
            if(last[pl]) nextt[last[pl]] = nextt[pl],
                        change(last[pl],nextt[pl]);
            change(pl,n+1);
            if(nextt[pl]) last[nextt[pl]] = last[pl];
            in[a[pl]].erase(pl);

            a[pl] = x;it = in[a[pl]].lower_bound(pl);
            if(it == in[a[pl]].end()) nextt[pl] = n + 1;
            else nextt[pl] = *it,last[nextt[pl]] = pl;
            if(it == in[a[pl]].begin()) last[pl] = 0;
            else last[pl] = *(--it),nextt[last[pl]] = pl;
            in[a[pl]].insert(pl);
            change(pl,nextt[pl]);
            if(last[pl]) change(last[pl],pl);
        }
    }
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值