AT1219 歴史の研究 回滚莫队

可在vj上提交:https://vjudge.net/problem/AtCoder-joisc2014_c

 

题意:

IOI 国历史研究的第一人——JOI 教授,最近获得了一份被认为是古代 IOI 国的住民写下的日记。JOI 教授为了通过这份日记来研究古代 IOI 国的生活,开始着手调查日记中记载的事件。

日记中记录了连续 NN 天发生的时间,大约每天发生一件。

事件有种类之分。第 ii 天发生的事件的种类用一个整数 X_iXi 表示,X_iXi 越大,事件的规模就越大。

JOI 教授决定用如下的方法分析这些日记:

  • 选择日记中连续的几天 [L,R][L,R] 作为分析的时间段;

  • 定义事件 AA 的重要度 W_AWA 为 A\times T_AA×TA,其中 T_ATA 为该事件在区间 [L,R][L,R] 中出现的次数。

现在,您需要帮助教授求出所有事件中重要度最大的事件是哪个,并输出其重要度。

注意:教授有多组询问。

输入格式

第一行两个空格分隔的整数 NN 和 QQ,表示日记一共记录了 NN 天,询问有 QQ 次。

接下来一行 NN 个空格分隔的整数表示每天的事件种类。

接下来 QQ 行,每行给出 L,RL,R 表示一组询问。

输出格式

输出共有 QQ 行,每行一个整数,表示对应的询问的答案。

数据范围

对于 100\%100% 的数据,1\le Q,N\le 10^51Q,N105,1\le X\le 10^91X109,1\le L\le R\le 10^51LR105。

输入输出样例

输入 #1
5 5
9 8 7 8 9
1 2
3 4
4 4
1 4
2 4
输出 #1
9
8
8
16
16

 

题解:

我们首先说一下普通莫队怎么做:
首先就是对区间排序,因为我们需要找一个区间内的最大值,这个我们可以用以变量tmp来记录。
如果要添加一个数,那么我们用cnt数组来记录某个数出现的次数,并更新tmp的值
但是如果要删除一个数,虽然我们的cnt数组还可以通过减去1来保证cnt数组正确性,但是我们没有办法去更新tmp
那么要是保证tmp正确性,我们就必须对这个区间重新遍历一次,而不能在上一个区间的前提下操作。
那么如果这样操作的话,复杂度就是n*n*sqrt(n),相信这个复杂度大多题目都会TLE了

 

回滚莫队解决它:
首先排序函数要改一下,如果两个查询区间的左边界不在一个块,那就按照左边界从小到大排序,否则那就按照右边界从小到大排序

ll cmp(query a, query b)
{
    return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : a.r < b.r;
}

 

对于一个查询区间[l,r],我们把l叫做左边界,把r叫做右边界。使用左标记和右标记来在n个数的位置上移动

这样,对于左边界属于一个块(我们把n个数分成sqrt(n)个块)的查询区间,因为这些查询区间的右边界是递增的,所以右标记就只会向右移动,不会向左,就意味着不会出现删除元素的情况


但是这些查询区间的左边界可不一定和右边界的位置对应,也就是说对于右边界添加元素和删除元素的情况都会出现,怎么解决呢?
我们就暴力去解决,每次把左标记移动到一个查询区间的左边界,操作完之后在移动原位置(这一点可以看代码)

for(; belong[que[i].l]==k; ++i) //在同一个块内
        {
            ll start=que[i].l,last=que[i].r;
            ll tmp;
            while(r<last)  //因为我们的排序函数,所以我们只需要r<last和l>start两种情况
            {
                ++r;
                ++cnt[typ[r]];
                now=max(now,1ll*cnt[typ[r]]*arr[r]);
            }
            tmp=now;
            while(l > start)  //将左标记回归到原位置
            {
                --l;
                ++cnt[typ[l]];
                now = max(now, 1ll * cnt[typ[l]] * arr[l]);
            }

            ans[que[i].id] = now;
            while(l < rb[k] + 1)
            {
                --cnt[typ[l]];
                l++;
            }
            now = tmp;
        }

 

这个复杂度就是n*sqrt(n)。因为每次左标记都是一个固定位置,我们移动到一个查询区间的左边界这个复杂度就是sqrt(n)
因为一共有m个查询,所以m个边界(题目上说了m<=n) 所以最大复杂度就是n*sqrt(n)


然后右边界可能不在这个块内,但是右标记不需要来回移动,它只需要一直向右移动(添加元素),所以复杂度n
因为最多也就sqrt(n)个块,所以复杂度最大就是n*sqrt(n)

所以复杂度也就是n*sqrt(n)

 

代码:

#include <map>
#include <set>
#include <list>
#include <queue>
#include <deque>
#include <cmath>
#include <stack>
#include <vector>
#include <bitset>
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 2e5+10;
const int INF = 0x3f3f3f3f;
const double PI = 3.1415926;
const long long N = 1000006;
const double eps = 1e-10;
typedef long long ll;
#define mem(A, B) memset(A, B, sizeof(A))
#define lson rt<<1 , L, mid
#define rson rt<<1|1 , mid + 1, R
#define ls rt<<1
#define rs rt<<1|1
#define SIS std::ios::sync_with_stdiget_mod_new(z-x)o(false), cin.tie(0), cout.tie(0)
#define pll pair<long long, long long>
#define lowbit(abcd) (abcd & (-abcd))
#define max(a, b) ((a > b) ? (a) : (b))
#define min(a, b) ((a < b) ? (a) : (b))
inline ll read()    //读取整数
{
    ll res = 0;
    char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) res = (res << 1) + (res << 3) + (c ^ 48), c = getchar();
    return res;
}
ll arr[maxn],cnt[maxn],cnt2[maxn],typ[maxn],inp[maxn],belong[maxn],lb[maxn],rb[maxn];
ll n,m,sizes,new_size;
ll ans[maxn];
struct query
{
    ll l,r,id;
} que[maxn];
ll cmp(query a, query b)
{
    return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : a.r < b.r;
}
int main()
{
    n=read(),m=read();
    sizes=sqrt(n);
    new_size=ceil((double)n/sizes);
    for(ll i = 1; i <= new_size; ++i)
    {
        lb[i] = sizes * (i - 1) + 1;
        rb[i] = sizes * i;
        for(ll j = lb[i]; j <= rb[i]; ++j)
            belong[j] = i;
    }
    rb[new_size]=n;
    for(ll i=1; i<=n; ++i)
        arr[i]=inp[i]=read();
    sort(inp+1,inp+1+n);
    ll tot=unique(inp+1,inp+1+n)-inp-1;
    for(ll i=1; i<=n; ++i)
    {
        typ[i]=lower_bound(inp+1,1+inp+tot,arr[i])-inp;
    }
    for(ll i=1; i<=m; ++i)
    {
        que[i].l=read();
        que[i].r=read();
        que[i].id=i;
    }
    sort(que+1,que+1+m,cmp);
    ll i=1;
    for(ll k=1; k<=new_size; ++k)
    {
        ll l=rb[k]+1,r=rb[k];
        ll now=0;
        mem(cnt,0);
        for(; belong[que[i].l]==k; ++i) //在同一个块内
        {
            ll start=que[i].l,last=que[i].r;
            ll tmp;
            if(belong[start]==belong[last])  //特判
            {
                tmp=0;
                for(ll j=start;j<=last;++j)
                {
                    cnt2[typ[j]]=0;
                }
                for(ll j=start; j<=last; ++j)
                {
                    cnt2[typ[j]]++;
                    tmp=max(tmp,1ll*cnt2[typ[j]]*arr[j]);
                }
                ans[que[i].id]=tmp;
                continue;
            }
            while(r<last)  //因为我们的排序函数,所以我们只需要r<last和l>start两种情况
            {
                ++r;
                ++cnt[typ[r]];
                now=max(now,1ll*cnt[typ[r]]*arr[r]);
            }
            tmp=now;
            while(l > start)
            {
                --l;
                ++cnt[typ[l]];
                now = max(now, 1ll * cnt[typ[l]] * arr[l]);
            }

            ans[que[i].id] = now;
            while(l < rb[k] + 1)
            {
                --cnt[typ[l]];
                l++;
            }
            now = tmp;
        }

    }
    for(ll i=1; i<=m; ++i) printf("%lld\n",ans[i]);
    return 0;
}
posted @ 2020-09-24 09:33  kongbursi  阅读(110)  评论(0编辑  收藏  举报