【练习】BZOJ4241: 历史研究(回滚莫队)

题意

给定一个长度为 n n 的序列,并提出q个询问,每次询问要求回答区间 [l,r] [ l , r ] 内所有的权值与其出现次数的积的最大值。

题解

我一看,区间问题,无修改可以离线,上莫队妥妥的,但是仔细一想,当区间扩充时,可以 O(1) O ( 1 ) 的求出新区间答案,但是当区间缩小时,就不能 O(1) O ( 1 ) 做到了,莫队算法的复杂度不能得到保证。

这时候就可以考虑回滚莫队,其思想还是对询问排序,并且对区间的双指针有优化。
普通的莫队算法,初始的 l,r l , r 就是第一个询问的位置的 [L,R] [ L , R ] ,之后对 l,r l , r 就没有人为的修改操作,只让他随着询问变化,在能够 O(1) O ( 1 ) 求出新区间答案的时候,这样的莫队算法保证了复杂度在 n1.5 n 1.5

此时,由于不能直接 O(1) O ( 1 ) 求出区间扩张(或收缩)的时候,需要手动调整这两个指针的位置,以达到回滚的操作。
第一步,对序列分块,询问排序。
第二步,指定ql为当前询问的l所在块的下一块的第一个元素,指定qr为当前询问l所在块的最后一个元素。对于每一个询问,我们要判断

  1. 如果当前询问的r也在l的块内,那么直接暴力统计区间答案,复杂度不超过 n n
  2. 如果当前询问的r不在l的块内,那么就让qr向右拓展,并暂存拓展结果,让ql向左拓展,然后统计区间
    答案。这一步和普通的莫队没有区别。

第三步,回滚。我们让ql回滚到当前l所在块的下一块第一个元素,为下一次询问做准备。恢复暂存的拓展结果

对于以上步骤,如果询问l所在块与上一个询问l所在块不同,就需要重新指定ql和pr。
由于在一个块内的询问,qr是不回退的,这就保证了复杂度!

如果还是不明白,可以看看下面的博客。
https://blog.csdn.net/MaxMercer/article/details/75576805
https://blog.csdn.net/qq_33330876/article/details/73522230

代码

#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef unsigned long long ull;
const int nmax = 1e5+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const ull p = 67;
const ull MOD = 1610612741;
int n,m;
vector<ll> v;
ll t;
ll a[nmax],inocc[nmax],glocc[nmax],ans;
int belong[nmax],idx[nmax],sz,R[nmax];
struct node{
    int l,r,id;
    ll ans;
}Q[nmax];
int getid(ll x){return lower_bound(v.begin(),v.end(),x) - v.begin() + 1;}
bool cmp(node a, node b){return (belong[a.l] == belong[b.l])?(a.r<b.r):(belong[a.l]<belong[b.l]);}
bool cmpid(node a, node b){return a.id < b.id;}
inline void modify(int pos,ll val){
    glocc[idx[pos]] ++;
    ans = max(ans,glocc[idx[pos]] * val);
}
inline void del(int pos){
    glocc[idx[pos]] --;
}
int main(){
    scanf("%d %d",&n,&m);
    int sz = sqrt(n);
    int num = n / sz; if(n%sz) num ++;
    for(int i = 1;i<=num;++i) R[i] = min(i*sz,n);
    for(int i = 1;i<=n;++i) scanf("%lld",&a[i]),v.push_back(a[i]);
    sort(v.begin(),v.end()), v.erase(unique(v.begin(),v.end()),v.end()); // 离散化
    for(int i = 1;i<=n;++i) idx[i] = getid(a[i]), belong[i] = (i-1) / sz + 1;
    for(int i = 1;i<=m;++i) scanf("%d %d",&Q[i].l,&Q[i].r), Q[i].id = i;
    sort(Q+1,Q+1+m,cmp);
    int now = belong[Q[1].l];
    int ql = R[now] + 1,qr = R[now];
    for(int i = 1;i<=m;++i){
        if(belong[Q[i].l] != now){
            for(int j = 1;j<=n;++j) glocc[j] = 0;
            now = belong[Q[i].l];
            ql = R[now] + 1, qr = R[now];
            ans = 0;
        }
        if(belong[Q[i].l] == belong[Q[i].r]){ //  l和r在同一块
            ll temp = 0;
            for(int j = Q[i].l;j<=Q[i].r;++j) inocc[idx[j]] = 0;
            for(int j = Q[i].l;j<=Q[i].r;++j) {
                inocc[idx[j]] ++; temp = max(temp,inocc[idx[j]] * a[j]);
            }
            Q[i].ans = temp;
        }else{ // l和r不在同一块
            while(qr<Q[i].r) modify(qr+1,a[qr+1]),qr++;
            ll temp = ans; // 暂存结果
            while(ql>Q[i].l) modify(ql-1,a[ql-1]),ql--;
            Q[i].ans = ans;
            while(ql<R[now]+1) del(ql),ql++; //回退
            ans = temp;
        }
    }
    sort(Q+1,Q+1+m,cmpid);
    for(int i = 1;i<=m;++i) printf("%lld\n",Q[i].ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值