「CNOI2019」数字游戏 题解

简述题意:
给定一个排列,多次询问,求一个区间 [ l , r ] [l,r] [l,r] 有多少个子区间的值都在区间 [ x , y ] [x,y] [x,y] 内。


算法1:

对于每个询问暴力枚举子区间,然后暴力检测是否满足条件,时间复杂度 O ( q n 3 ) O(qn^3) O(qn3)

用ST表预处理, O ( 1 ) O(1) O(1)查询区间最值可以做到 O ( q n 2 + n l o g n ) O(qn^2+nlogn) O(qn2+nlogn)

期望得分: 0 0 0

算法2:

我们发现查询一个区间时,其中每个满足所有元素都在 [ x , y ] [x,y] [x,y] 内的 极长子区间 的贡献为 t ( t + 1 ) 2 \frac{t(t+1)}{2} 2t(t+1), 其中 t t t 为该子区间的长度。

于是对于每次询问时扫描出所有极长子区间即可做到 O ( q n ) O(qn) O(qn)

期望得分: 0 0 0 ~ 34 34 34

(虽然构造数据卡了,但还是被一位小常数玩家卡过去了第一档分)

算法3:

很容易想到要使用数据结构维护,于是我们想到了莫队,但是 值域 [ x , y ] [x,y] [x,y] 一直在变,似乎很难维护,所以我们选择转变思路。

由于题目给的序列时一个排列,所以我们考虑在值域上莫队,每次移动只会改变一个位置是否有效。然后考虑维护答案,很显然可以线段树。每个节点维护一下
左、右端开始的极长有效区间长度,区间答案,与区间是否全部有效即可。

时间复杂度 O ( n q   l o g n ) O(n\sqrt{q}\ logn) O(nq  logn)

期望得分: 34 34 34

算法4:

考虑另一种 O ( q n ) O(qn) O(qn)的做法,每次询问扫描值域,判断值域内每个值是否在区间内,在就更新答案。更新答案时需要用链表维护一下每个极大有效区间。

期望的分 0 0 0

算法5:

发现时间复杂度的瓶颈在于查询时需要线段树,考虑优化。

所以我们对序列分块,结合算法四的方法,每个块内单独维护一个链表,和区间的答案,由于维护的信息难以撤销,所以我们选择回滚莫队维护值域,区间长度小于等于 2 n 2\sqrt{n} 2n 的使用算法4暴力,大于的部分每次 O ( n ) O(\sqrt{n}) O(n )查询,每次莫队移动时 O ( 1 ) O(1) O(1)维护所在块的链表,回滚时使用时间戳 O ( 1 ) O(1) O(1)回溯。

总体时间复杂度 O ( n n ) O(n\sqrt{n}) O(nn ),代码量4k左右。

期望得分: 100 100 100

总体来说这是一道简单的题,解法自然,码量适中,思维难度适中,考察了线段树、莫队、分块、链表等多种初等数据结构,是一道不折不扣的小清新数据结构好题。

附std:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

LL que_range( int x ) { return 1ll * x * (x + 1) / 2; }

template<int _size>
struct stamp{
    int A[ _size ];
    int B[ _size ];
    int T[ _size ];
    int cnt = 1;
    bool bind = false;
    
    void Roll() { cnt ++; }
    void Bind() { bind = true; Roll(); }
    void Unte() { bind = false; }
    
    void Set0() { memset( B, 0, sizeof(B) ); Roll(); }
    
    int& operator []( int index ) {
        if( bind ) {
            if( T[index] ^ cnt ) T[index] = cnt, A[index] = B[index];
            return A[index];
        } else return B[ index ];	
    }
};

int block = 500;
int belong[200005];
int beg[405];

struct que{
    int l, r, x, y, id;	
    que() {}
    que( int _id ) { cin >> l >> r >> x >> y; id = _id; }
    bool operator <( que& from ) const
      { return belong[x] == belong[ from.x ] ? y < from.y : x < from.x; }
};

int n, q; 
vector<que> Q;

int c [200005];
int uc[200005];

stamp<200005> pre;
stamp<200005> nxt;
stamp<405> mid;

int bf1( que& x ) { 
    int ans = 0, lst = 0;
    for( int i = x.l; i <= x.r; i ++ ) {
        if( c[i] <= x.y and c[i] >= x.x ) lst ++;
        else lst = 0;
        ans += lst;
    }
    return ans;
}

int bf2( que& x ) {
    int ans = 0;
    for(int i = x.x;i <= x.y;i ++) {
        int p = uc[i];
        if( p < x.l or p > x.r ) continue;
        ans -= que_range( p - pre[p - 1] );
        ans -= que_range( nxt[p + 1] - p );
        nxt[ pre[p - 1] ] = nxt[p + 1];
        pre[ nxt[p + 1] ] = pre[p - 1];
        ans += que_range( nxt[p + 1] - pre[p - 1] + 1 );
    }
    pre.Roll(); nxt.Roll();
    return ans;
}

void updata( int x ) {
    int p = uc[x], b = belong[p];
    if( p ^ beg[b] ) {
        if( pre[p - 1] != beg[b] ) mid[b] -= que_range( p - pre[p - 1] );
        if( p != beg[b + 1] - 1 )nxt[ pre[p - 1] ] = nxt[p + 1];
        else nxt[p] = p, pre[p] = pre[p - 1], nxt[ pre[p - 1] ] = p;
    }
    if( p ^ beg[b + 1] - 1 ) {
        if( nxt[p + 1] != beg[b + 1] - 1 ) mid[b] -= que_range( nxt[p + 1] - p );
        if( p != beg[b] ) pre[ nxt[p + 1] ] = pre[p - 1];
        else pre[p] = p, nxt[p] = nxt[p + 1], pre[ nxt[p + 1] ] = p;
    }
    if( p != beg[b] and p != beg[b + 1] - 1 and pre[p - 1] != beg[b] and nxt[p + 1] != beg[b + 1] - 1 ) 
        mid[b] += que_range( nxt[p + 1] - pre[p - 1] + 1 );
}

LL ans[200005];

int main(){
    cin >> n >> q; 
    
    for(int i = 1;i <= n;i ++) {
        cin >> c[i]; uc[ c[i] ] = i;
        belong[i] = i / block + 1;
        if( belong[i] > belong[i - 1] ) beg[ belong[i] ] = i;
    }
    if( beg[ belong[n] ] == n ) belong[n] --;
    beg[ belong[n] + 1 ] = n + 1; belong[ n + 1 ] = belong[n] + 1;
    
    for(int i = 0;i <= n + 1;i ++) pre[i] = i + 1, nxt[i] = i - 1;
    nxt.Bind(); pre.Bind(); mid.Bind();
    
    for(int i = 1;i <= q;i ++) {
        que x = que(i);
        if     ( x.r - x.l <= block * 2 ) ans[i] = bf1( x );
        else if( x.y - x.x <= block * 2 ) ans[i] = bf2( x ); 
        else Q.push_back( x );
    }
    sort( Q.begin(), Q.end() );
    int pt = 0;
    for(int i = 1;i < belong[n];i ++) {
        int r = beg[i + 1] - 1;
        
        nxt.Unte(); pre.Unte(); mid.Unte();
        for(int j = 0;j <= n + 1;j ++) pre[j] = j + 1, nxt[j] = j - 1; mid.Set0();
        
        while( pt < Q.size() and belong[ Q[pt].x ]  == i ) {
            auto &P = Q[pt];
            
            nxt.Unte(); pre.Unte(); mid.Unte();
            while( r < P.y ) updata( ++ r );
            
            nxt.Bind(); pre.Bind(); mid.Bind();
            for( int j = P.x; j < beg[i + 1]; j ++ ) updata( j );
            
            long long Ans = 0, lst = 0; 
            int bl = belong[ P.l - 1 ] + 1;
            int br = belong[ P.r + 1 ] - 1;
            
            for( int j = P.l; belong[j] < bl; j ++ ) {
                if( c[j] >= P.x and c[j] <= P.y ) lst ++;
                else Ans += que_range( lst ), lst = 0;
            }
            
            for( int j = bl; j <= br; j ++ ) {
                if( nxt[ beg[j] ] == beg[j + 1] - 1 ) lst += beg[j + 1] - beg[j];
                else {
                    lst += nxt[ beg[j] ] - beg[j] + 1;
                    Ans += que_range(lst); Ans += mid[j];
                    lst = beg[j + 1] - pre[ beg[j + 1] - 1 ];
                }
            }
            
            for( int j = beg[br + 1]; j <= P.r; j ++ ) {
                if( c[j] >= P.x and c[j] <= P.y ) lst ++;
                else Ans += que_range( lst ), lst = 0;
            }
            
            Ans += que_range( lst );
            ans[ P.id ] = Ans;
            
            nxt.Roll(); pre.Roll(); mid.Roll();
            pt ++;
        }
    }
    for(int i = 1;i <= q;i ++) cout << ans[i] << "\n";
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值