BZOJ 4241 历史研究 (回滚莫队)

4 篇文章 0 订阅

4241: 历史研究

Time Limit: 80 Sec Memory Limit: 512 MB
Description

IOI国历史研究的第一人——JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记。JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件。
日记中记录了连续N天发生的时间,大约每天发生一件。
事件有种类之分。第i天(1<=i<=N)发生的事件的种类用一个整数Xi表示,Xi越大,事件的规模就越大。
JOI教授决定用如下的方法分析这些日记:
1. 选择日记中连续的一些天作为分析的时间段
2. 事件种类t的重要度为t*(这段时间内重要度为t的事件数)
3. 计算出所有事件种类的重要度,输出其中的最大值
现在你被要求制作一个帮助教授分析的程序,每次给出分析的区间,你需要输出重要度的最大值。
4.
Input

第一行两个空格分隔的整数N和Q,表示日记一共记录了N天,询问有Q次。
接下来一行N个空格分隔的整数X1…XN,Xi表示第i天发生的事件的种类

Output

输出Q行,第i行(1<=i<=Q)一个整数,表示第i次询问的最大重要度

Sample Input

5 5

9 8 7 8 9

1 2

3 4

4 4

1 4

2 4

Sample Output

9

8

8

16

16

HINT

1<=N<=10^5

1<=Q<=10^5

1<=Xi<=10^9 (1<=i<=N)

题目大意:
有一个长度为n的序列。
有m个询问,每次询问l~r范围内每个数值乘以该数值出现次数的最大值。

思路:
听起来很玄奥的回滚莫队。
回滚莫队可以代替掉删除操作,让时间复杂度仍然保持在O(nsqrtn)。
分块和排序都按照基础莫队做法来,然后在统计答案的时候,如果一个询问的左端点和右端点在同一个块内,那就暴力统计。
然后对于左端点在同一块内的询问我们一起统计,首先让左端点在这一块的最右端,然后让右端点正常向右扩张。当右端点满足条件的时候,记录一下这个时候的状态,再把左端点调整到询问的左端点,这个时候统计一下答案,然后再回滚到没有调整左端点的时候的那个状态。然后再做下一个询问就可以了。
莫队。。。真是个卡暴力的好(e xin)方法

#include <cstdio>
#include <cstring>
#include <cmath> 
#include <algorithm>
#define N 120000
#define LL long long
using namespace std;

int n, m, block, pos;
int a[N], aa[N], place[N], top;
LL sum[N], num[N], pre, now, ans[N];

struct Query{
    int l, r, id;
}q[N];

bool cmp ( Query aa, Query bb ){//块内r,块外l 
    return place[aa.l] < place[bb.l] || (place[aa.l] == place[bb.l] && aa.r < bb.r); 
}

void adde(int c){
    sum[c] += aa[c];//+-的是aa中的原值 
    now = max(now, sum[c]);
}

void del(int c){
    sum[c] -= aa[c];
}

int main(){
    scanf("%d%d", &n, &m);
    block = (int) sqrt(n);
    for(int i=1; i<=n; i++){
        scanf("%d", &a[i]);
        aa[i] = a[i];
    }
    sort(aa+1, aa+1+n);
    top = unique(aa+1, aa+1+n) - aa;//去重 
    for(int i=1; i<=n; i++)
        a[i] = lower_bound(aa+1, aa+top, a[i]) - aa;//离散化 
    for(int i=1; i<=m; i++){
        scanf("%d%d", &q[i].l, &q[i].r);
        q[i].id = i;//query排序 
    }
    for(int i=1; i<=n; i++)
        place[i] = (i-1) / block + 1;
    sort(q+1, q+1+m, cmp);
    int l, r;
    for(int i=1; i<=m; i++){
        if(place[q[i].l] != place[q[i-1].l]){
            memset(sum, 0, sizeof(sum));
            pre = now = 0;
            l = pos = place[q[i].l] * block + 1;//l放到块的左端点上 
            r = l - 1;//保证初值为零 
        }
        if(place[q[i].l] == place[q[i].r]){//l,r在同一块中,不移动l,r暴力query 
            LL cur = 0;
            for(int j=q[i].l; j<=q[i].r; j++){
                num[a[j]] += aa[a[j]];
                cur = max(cur, num[a[j]]);
            }
            for(int j=q[i].l; j<=q[i].r; j++)//还原 
                num[a[j]] -= aa[a[j]];
            ans[q[i].id] = cur;
            continue;

        }
        while(r < q[i].r) adde( a[++r] );//添加信息 
        pre = now;//记录当前状态 
        while(l > q[i].l) adde( a[--l] );
        ans[q[i].id] = now;//记录答案 
        while(l < pos) del( a[l++] );//还原(回滚) 
        now = pre;
    }
    for(int i=1; i<=m; i++) 
        printf("%lld\n", ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值