BZOJ4241 历史研究 (分块 回滚莫队-教程向)

题目大意

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


题解

看见这种xjb询问的题当然是要下意识地用分块来搞一搞的,又因为只有询问,且题目没有要求强制在线,所以就可以理所应当地“分块+莫队”来搞这道题了。

对于这道题,莫队的加入操作是很好实现的,只要增加一下被加入区间的权值的出现次数并同时更新一下答案就可以了,但是删除操作却很难实现,因为删除操作后无法O(1)地更新答案。这时一位id为alone_wolf的神犇为我提供了一个思路:实现没有删除操作,只有加入操作的回滚式莫队算法。

回滚式莫队是以询问的左端点所在的块为第一关键字,右端点为第二关键字对询问进行排序的。考虑枚举每一个块,枚举这个块时处理左端点在这个块内的所有询问。因为对于左端点在相同块内的询问,其右端点一定是递增的(因为就是这么排序的)。

对于询问的右端点和左端点在同一块内的,询问的大小一定小于 n ,所以可以直接暴力统计,时间复杂度为 n

对于其余的询问,因为右端点是单调向右的,所以不会出现左右摆动的情况,因此对于当前块右端点右边的部分只要单调地加入就可以了,因为只会单调地加入元素,所以这部分的总时间复杂度为 n 。其余的部分的大小一定小于 n ,所以每次暴力地统计左侧的部分来更新答案,每个询问需要 n 的时间,记录答案后将左侧部分造成的影响还原再进行下一个询问的统计。

时间复杂度 Onn ,暂时是bzoj的rank1:


BZOJ4241 历史研究 (分块 回滚莫队-教程向)_第1张图片


代码

/**************************************************************
    Problem: 4241
    User: CHN
    Language: C++
    Result: Accepted
    Time:9968 ms
    Memory:5592 kb
****************************************************************/

#include 
#include 
#include 
using namespace std;

inline int read() {
    register int val=0; char ch;
    while(~(ch=getchar()) && (ch<'0' || ch>'9')); val=ch-'0';
    while(~(ch=getchar()) && (ch>='0' && ch<='9')) val=(val<<1)+(val<<3)+ch-'0';
    return val;
}

const int maxn=int(1e5)+111;
int n,m;
int a[maxn], b[maxn], v[maxn];
int siz, num, bel[maxn];
long long ans[maxn];

struct Q {
    int l,r,id;
    bool operator < (const Q &b) const {
        return bel[l]==bel[b.l]?rq[maxn];

void Read() {
    register int i;
    n=read(), m=read();
    for(i=1;i<=n;++i)
        v[i]=a[i]=read();

    sort(v+1,v+1+n);
    int nn=unique(v+1,v+1+n)-(v+1);
    for(i=1;i<=n;++i)
        b[i]=lower_bound(v+1,v+1+nn,a[i])-v;

    for(i=1;i<=m;++i) {
        q[i].l=read(), q[i].r=read();
        q[i].id=i;
    }
    while(siz*sizfor(i=1;i<=n;++i)
        bel[i]=(i-1)/siz+1, num=max(num,bel[i]);
    sort(q+1,q+1+m);
    return;
}

long long tmp=0;
int cnt[maxn];

void Add(int id) {
    ++cnt[b[id]];
    tmp=max(tmp,1ll*cnt[b[id]]*a[id]);
}
void Del(int id) {
    --cnt[b[id]];
}

long long small(int l,int r) {
    static int cnt2[maxn];
    long long res=0;
    register int i;
    for(i=l;i<=r;++i)
        cnt2[b[i]]=0;
    for(i=l;i<=r;++i) {
        ++cnt2[b[i]];
        res=max(res,1ll*cnt2[b[i]]*a[i]);
    }
    return res;
}

int Mo(int pos,int id) {
    int L=min(id*siz,n);
    register int i=pos,j, ql=L+1, qr=ql-1;
    for(j=1;j<=n;++j) cnt[j]=0;
    tmp=0;
    for(;bel[q[i].l]==id;++i) {
        if(bel[q[i].l]==bel[q[i].r]) {
            ans[q[i].id]=small(q[i].l,q[i].r);
            continue;
        }
        while(qrq[i].l) Add(--ql);
        ans[q[i].id]=tmp;
        while(ql1) Del(ql++);
        tmp=cur;
    }
    return i;
}

void Solve() {
    register int i,pos=1;
    for(i=1;i<=num;++i)
        pos=Mo(pos,i);
    return;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt","r",stdin);
//  freopen("output.txt","w",stdout);
#endif
    Read();
    Solve();

    for(int i=1;i<=m;++i)
        printf("%lld\n",ans[i]);

    return 0;
}

你可能感兴趣的:(分块,莫队算法)