数列分块&st表

数列分块:

数列分块的思想就是通过把数列分成块状,整体上通过块来对数列进行操作,同时在做有些操作的时候有一点lazy标记的思想,而且一般都是多定义数组来解决不同的功能。这个和线段树解决的问题差不多,但是不得不承认在有些问题的解决上线段树的代码两确实太大了。

ST表:

算法的主题是倍增。常用来处理RMQ问题。一般开 a[ i ][ j ]表示从下标 i 开始,长度为 2^j的子数组最值是多少。步骤一般是 dp预处理 查询操作。ST表的复杂度为:预处理 O( NlogN); O( 1 ).具体见下面的模板题代码。

A - 数列分块入门 1

题目描述:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,单点查值。第一行输入一个数字 n。第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。接下来输入 n 行询问,每行输入四个数字 opt、l、r、c,以空格隔开。若 opt=0,表示将位于 [l,r]的之间的数字都加 c。若 opt=1,表示询问 ar 的值(l 和 c 忽略)。

思路:就是个数列分块的板题目。

#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 5e4 + 10;
ll n, a[N], lazy[N], id[N], op, l, r, c;
void update( ll l, ll r, ll val ){
    ll L = id[l], R = id[r];
    if( L == R ){//如果是在一个块内,就直接暴力更新,因为5e4开方后也只有两百多,一般不会被T
        for( ll i = l; i < r + 1; i++ ) a[i] += val;
        return ;
    }
    // 分开来处理,分为零碎的左边,右边,和完整的中间块
    for( ll i = l; id[i] == L; i++ ) a[i] += val;// 零碎的部分直接暴力
    for( ll i = L + 1; i < R; i++ ) lazy[i] += val;// 对这整个区间进行lazy标记
    for( ll i = r; id[i] == R; i-- ) a[i] += val;
}

ll query( ll r ){// 单点查询
    return a[r] + lazy[id[r]];//下发lazy标记
}

int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n;
    ll len = sqrt( n );
    for( ll i = 1; i < n + 1; i++ ){
        cin >> a[i];
        id[i] = ( i - 1 ) / len + 1;// 这相当于是模板来定义块的下标
    }
    for( ll i = 0; i < n; i++ ){
        cin >> op >> l >> r >> c;
        if( op ) cout << query( r ) << "\n";
        else update( l, r, c );    
    }
    return 0;
}

C - 数列分块入门 4

 给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,区间求和。第一行输入一个数字 n。第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。接下来输入 n 行询问,每行输入四个数字 opt、l、r、c,以空格隔开。若 opt=0,表示将位于 [l,r]的之间的数字都加 c。若 opt=1,表示询问 l到r 的值 mod( c + 1 )。

思路:本质就是 在上面的题的基础上通过 增加一个数组 来 新增一个功能。

// 当时错那么多还是因为对于数列分块没有真的了解意义,没有了解块lazy标记的含义
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 5e4 + 10;
ll n, a[N], lazy[N], id[N], op, l, r, c, sum[N], len;
void update( ll l, ll r, ll val ){
    ll L = id[l], R = id[r];
    if( L == R ){
        for( ll i = l; i < r + 1; i++ ){
            a[i] += val;
            sum[L] += val;// L块需要总和更新
        }
        return ;//注意要及时return,以及return的位置
    }
    for( ll i = l; id[i] == L; i++ ){
        a[i] += val;
        sum[L] += val;
    }
    for( ll i = L + 1; i < R; i++ ){
        lazy[i] += val;// 注意只能对整块进行lazy标记。
    }
    for( ll i = r; id[i] == R; i-- ){
        a[i] += val;
        sum[R] += val;
    }
}

ll query( ll l, ll r, ll MOD ){
    ll ans = 0;
    ll L = id[l], R = id[r];
    if( L == R ){
        for( ll i = l; i < r + 1; i++ ){
            ans += ( a[i] + lazy[L] ) % MOD;
            // 注意要下发lazy标记,因为我在做块更新操作的时候实际上没有对a数组进行更新操作,
            //只是对lazy标记进行了更新,而对a数组没有影响。可能这次询问的就是前面的整块
            ans %= MOD;

        }
        return ans % MOD;
    }
    for( ll i = l; id[i] == L; i++ ){
        ans += ( a[i] + lazy[L] ) % MOD;
        ans %= MOD;
    }// 同理
    for( ll i = L + 1; i < R; i++ ){
        ans += ( sum[i] + lazy[i] * len ) % MOD;// 注意这里是sum
        ans %= MOD;
    }
    for( ll i = r; id[i] == R; i-- ){
        ans += ( a[i] + lazy[R] ) % MOD;
        ans %= MOD;
    }  // 同理
    return ans % MOD;
}

int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n;
    len = sqrt( n );
    for( ll i = 1; i < n + 1; i++ ){
        cin >> a[i];
        id[i] = ( i - 1 ) / len + 1;
        sum[id[i]] += a[i];// 代表每个块的总和
    }
    for( ll i = 0; i < n; i++ ){
        cin >> op >> l >> r >> c;
        if( op ) cout << query( l, r, c + 1 ) << "\n"; //注意是mod( c + 1 );
        else update( l, r, c );    
    }
    return 0;
}

贴上错误代码

#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 5e4 + 10;
ll n, a[N], lazy[N], id[N], op, l, r, c, sum[N], len;
void update( ll l, ll r, ll val ){
    ll L = id[l], R = id[r];
    if( L == R ){
        for( ll i = l; i < r + 1; i++ ){
            a[i] += val;
            sum[L] += val;
        }
        return ;
    }
    for( ll i = l; id[i] == L; i++ ){
        a[i] += val;
        sum[L] += val;
    }
    for( ll i = L + 1; i < R; i++ ){
        lazy[i] += val;
        sum[i] += val * len;
    }
    for( ll i = r; id[i] == R; i-- ){
        a[i] += val;
        sum[R] += val;
    }
}

ll query( ll l, ll r, ll MOD ){
    ll ans = 0;
    ll L = id[l], R = id[r];
    if( L == R ){
        for( ll i = l; i < r + 1; i++ ){
            ans += a[i];
        }
        return ans % MOD;
    }
    for( ll i = l; id[i] == L; i++ ){
        ans += a[i];
    }
    for( ll i = L + 1; i < R; i++ ){
        ans += sum[i];
    }
    for( ll i = r; id[i] == R; i-- ){
        ans += a[i];
    }  
    return ans % MOD;
}

int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n;
    len = sqrt( n );
    for( ll i = 1; i < n + 1; i++ ){
        cin >> a[i];
        id[i] = ( i - 1 ) / len + 1;
        sum[id[i]] += a[i];
    }
    for( ll i = 0; i < n; i++ ){
        cin >> op >> l >> r >> c;
        if( op ) cout << query( l, r, c + 1 ) << "\n";
        else update( l, r, c );    
    }
    return 0;
}

B - 数列分块入门 2

题目描述:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x的元素个数。第一行输入一个数字 n。第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。接下来输入 n 行询问,每行输入四个数字 opt、l、r、c,以空格隔开。若 opt=0,表示将位于 [l,r]的之间的数字都加 c。若 opt=1,表示询问 l到r 的值小于c^2数字的个数。

思路:这道题的难点在于操作1,计算满足条件的个数,暴力铁超时,这个时候我们采用 二分查找,但是要二分必须保证数组是有序的,我们不能在原数组上进行操作,所以要重新开一个数组,但是这个数组维护的是块区间内有序。

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
ll n, a[N], lazy[N], id[N], op, l, r, c, len, b[N];
void reset( ll l, ll r ){
    for( ll i = l; i < r + 1; i++ ){
        b[i] = a[i];
    }
    sort( b + l, b + r + 1 );// 注意sort排序的特点是前闭后开,维护单个区间有序
}

void update( ll l, ll r, ll val ){
    ll L = id[l], R = id[r];
    if( L == R ){
        for( ll i = l; i < r + 1; i++ ){
            a[i] += val;
        }
        reset( ( L - 1 ) * len + 1, L * len );
        // 注意放的位置,是加完再整体sort,不然会提高复杂度导致超时
        return ;
    }
    for( ll i = l; id[i] == L; i++ ){
        a[i] += val;
    }
    reset( ( L - 1 ) * len + 1, L * len );//同理
    for( ll i = L + 1; i < R; i++ ){
        lazy[i] += val;
    }// 注意这是没必要的,因为都加上一个数,顺序不会改变
    for( ll i = r; id[i] == R; i-- ){
        a[i] += val;
    }
    reset( ( R - 1 ) * len + 1, R * len );//同理
}

ll query( ll l, ll r, ll val ){
    ll cnt = 0;
    ll L = id[l], R = id[r];
    if( L == R ){
        for( ll i = l; i < r + 1; i++ ){
            if( ( a[i] + lazy[L] ) < val ) ++cnt;// 记得下发lazy
        }
        return cnt;
    }
    for( ll i = l; id[i] == L; i++ ){
        if( ( a[i] + lazy[L] ) < val ) ++cnt;
    }
    for( ll i = L + 1; i < R; i++ ){
        ll bl = ( i - 1 ) * len + 1, br = i * len;
        cnt += ( lower_bound( b + bl, b + br + 1, val - lazy[i] ) -  b - bl );// !!!
    }
    for( ll i = r; id[i] == R; i-- ){
        if( ( a[i] + lazy[R] ) < val ) ++cnt;
    }  
    return cnt;
}

int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n;
    len = sqrt( n );
    for( ll i = 1; i < n + 1; i++ ){
        cin >> a[i];
        id[i] = ( i - 1 ) / len + 1;
    }
    for( ll i = 1; i < id[n] + 1; i++ ){
        reset( ( i - 1) * len + 1, i * len  );
        // 这里有计算块的第一个数下标,和最后一个数下标的方法,让块内有序
        if( i == id[n] ){
          reset( ( i - 1) * len + 1, n );  
        }//注意最后一个块可能长度和前面的不同意,所以,我们需要对最后一个块单独处理,
        //虽然有一点点浪费前面的最后一次排序
    }
    for( ll i = 0; i < n; i++ ){
        cin >> op >> l >> r >> c;
        if( op ) cout << query( l, r, c * c) << "\n";
        else update( l, r, c );    
    }
    return 0;
}

D - Balanced Lineup

 题目描述:FJ 的 N 头牛总是按同一序列排队。有一天,FJ 决定让一些牛玩一场飞盘比赛。他准备找一群在对列中为置连续的牛来进行比赛,但是为了避免水平悬殊,牛的身高不应该相差太大。FJ 准备了 Q 个可能的牛的选择和所有牛的身高。他想知道每一组里面最高和最低的牛的身高差别。第一行:N 和 Q; 第二至第 N+1 行,第 i+1 行是第 i 头牛的身高 hi; 第 N+2 至第 N+Q+1 行,每行两个整数 A 和 B,表示从 A 到 B 的所有牛。

思路:就是个模板题,但是要注意的是这个要同时求最大最小,所以我开了一个结构体,来存储最大最小值。

// 具体证明可以看看网上的其他博客
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 5e4 + 10;// 我也不知道为什么要开这么大???
struct node{
    ll minn, maxx;
}a[N][20];
ll n, q, A, B; 
void ini( ){
    for( ll j = 1; j <= 20; j++ ){
        // 放在前面是因为从转移方程式可知j要在i前面被推出来,长度由N定
        for( ll i = 1; i + ( 1 << j ) - 1 < n + 1; i++ ){
            a[i][j].minn = min( a[i][j - 1].minn, a[i + ( 1 << ( j - 1 ) )][j - 1].minn );
            a[i][j].maxx = max( a[i][j - 1].maxx, a[i + ( 1 << ( j - 1 ) )][j - 1].maxx );
        }
    }
}
ll query( ll l, ll r ){
    ll k = log2( r - l + 1 );
    return max( a[l][k].maxx, a[r - ( 1 << k ) + 1][k].maxx ) - min( a[l][k].minn, a[r - ( 1 << k ) + 1][k].minn );
}
int main(){
    ios :: sync_with_stdio( false );
    cin.tie( NULL );
    cin >> n >> q;
    for( ll i = 1; i < n + 1; i++ ){
        cin >> a[i][0].minn;
        a[i][0].maxx = a[i][0].minn;// 初始化
    }
    ini( );
    while( q-- ){
        cin >> A >> B;
        cout << query( A, B ) << "\n";
    }
    return 0;
}

总结:

其实线段是,ST表,数列分块,都是围绕区间开展,只是线段树书写起来很麻烦,在某些情况下后两者可以起到书写方便,理解简单的作用。特别是ST表,对于区间最值问题很方便,复杂度也比较好。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值