数据结构专题 - 解题报告 - F

我一开始是直接把E的代码改改就交的一发过,在线分块做法,286ms的速度很优秀,后来讲题时遇到新的算法——莫队,这题本来是专门为离线做法准备的,虽然看起来并没有卡在线做法,但还是得学一下。
先谈谈我对莫对算法的理解吧,都说莫队是一种优雅的暴力,用于求解区间内问题,因为区间在不停变换,我们如果用两个指针维护我们需要查询的区间(l 和 r),有以下三种情况。

  1. 无序,让l和r乱跳,时间O(n^2q)爆TLE!
  2. 排序后莫队,让l有序,r乱跳,时间O(NQ*LG(N)) 还是TLE!
  3. 考虑分块优化,如果快排时的左端点l和右端点r在同一区间内,即l div sqrt(n)=r div sqrt(n)。
    那么l,r在同一区间,直接以 query[ ].r 数组排序为第一关键字排序即可。
    否则以 query[ ].l 数组为第一关键字排序。

分块后让两个指针在区间板块内左右横跳,这就是莫队的想法。

因为要排序,提前需要准备的是写一个排序规则的函数,规则说过了,代码如下

struct node{			//在构造节点时重载一下node类型的排序规则就行了
    int l, r, id;
    bool operator < (const node &x)
    	{return l/divlen == x.l/divlen ? r < x.r : l < x.l;}
}query[maxn];

其中id是代表当前询问是原询问序列第几位,因为离线排过序,但是输出还是要遵循原序列顺序,所以需要用id来打标记,在建立一个辅助数组ans[ ]来依次存入ans[query[ ].id]的答案。


前置准备到此为止,现在上莫队核心代码:

FOR(i, 1, m)        //来自莫队的左右横跳
    {
        while(r < query[i].r) Add(++r);
        while(l > query[i].l) Add(--l);
        while(r > query[i].r) Erase(r--);
        while(l < query[i].l) Erase(l++);
        ans[query[i].id] = ret;
    }

排过序后每次移动到对应的左右端点,这个过程中答案的修改用ret存,最后放在ans[ ]里,修改规则用Add()函数和Erase()函数存,比如右指针右移至右端点,这个过程中存在判断添加进区间的新点的过程,称为Add,其他情况同理思考。
我的Add()和Erase()函数写法如下:

//cnt[i]表示i这个数在[l,r]区间中出现的次数,sum[i]表示 区间中cnt[x]=i的数的个数
void Add(int pos){
    int x = a[pos];
    sum[cnt[x]]--;		
    sum[++cnt[x]]++;
    ret = max(ret, cnt[x]);
    return;
}
void Erase(int pos){
    int x = a[pos];
    sum[cnt[x]]--;
    if(ret == cnt[x])				//如果当前的数就是最大数ret,那么就有对应的删减判断措施
        if(sum[cnt[x]]==0)		//如果最大数的副本数量已经减为零
            ret--;
    sum[--cnt[x]]++;
    return;
}

AC代码如下:

#include<bits/stdc++.h>
#define maxn 200005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define max(a, b) a>b?a:b
using namespace std;
const int divlen = 405;
const int mdiv = maxn/divlen + 5;

int n, m, k, l=1, r;
ll ret, ans[maxn];
int pre_lsh[maxn], a[maxn], a1[maxn];
int tot;
int cnt[maxn], sum[maxn];

inline int read(){
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

struct node{
    int l, r, id;
    bool operator < (const node &x)
    {
        return l/divlen == x.l/divlen ? r < x.r : l < x.l;
    }
}query[maxn];

void discret(){    //离散化
    sort(pre_lsh+1, pre_lsh+1+n);
    tot = unique(pre_lsh+1, pre_lsh+1+n) - (pre_lsh+1);
    FOR(i, 1, n)
        a[i] = lower_bound(pre_lsh+1, pre_lsh+1+tot, a1[i]) - pre_lsh;
}

void Add(int pos){
    int x = a[pos];
    sum[cnt[x]]--;
    sum[++cnt[x]]++;
    ret = max(ret, cnt[x]);
    return;
}
void Erase(int pos){
    int x = a[pos];
    sum[cnt[x]]--;
    if(ret == cnt[x])
        if(sum[cnt[x]]==0)
            ret--;
    sum[--cnt[x]]++;
    return;
}

int main()
{
    n = read();
    m = read();
    FOR(i, 1, n)
    {
        a1[i] = read();
        pre_lsh[i] = a1[i];
    }
    discret();
    FOR(i, 1, m)
    {
        query[i].l = read();
        query[i].r = read();
        query[i].id = i;
    }
    sort(query+1, query+1+m);
    FOR(i, 1, m)        //来自莫队的左右横跳
    {
        while(r < query[i].r) Add(++r);
        while(l > query[i].l) Add(--l);
        while(r > query[i].r) Erase(r--);
        while(l < query[i].l) Erase(l++);
        ans[query[i].id] = ret;
    }
    FOR(i, 1, m)
        printf("%lld\n", ans[i]);
    return 0;
}

不愧是离线算法,我来康康跑了多久啊,1084ms……
好吧我的莫队写的太垃圾了,离线处理连在线都跑不过,wsl
这里贴一发在线做法(同E题):

#include<bits/stdc++.h>
#define maxn 200005
#define maxm 200005
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define max(a, b) a>b?a:b
using namespace std;
const int divlen = 405;    //分块长度略小于sqrt(maxn)
const int mdiv = maxn/divlen + 5;

int pre_lsh[maxn], a[maxn], a1[maxn], temp, ans;
int n, m, ql, qr, lb, rb, l[maxn], r[maxn], bel[maxn], tot, divnum;
int id[maxn];
vector<int> v[maxn];
int cnt[maxn], MAX[mdiv][mdiv];

inline int read(){
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

void discret(){    //离散化
    sort(pre_lsh+1, pre_lsh+1+n);
    tot = unique(pre_lsh+1, pre_lsh+1+n) - (pre_lsh+1);
    FOR(i, 1, n)
    {
        a[i] = lower_bound(pre_lsh+1, pre_lsh+1+tot, a1[i]) - pre_lsh;
        id[i] = v[a[i]].size();     //id[i]该数在vector中的位置
        v[a[i]].push_back(i);       //vector存储每种数出现位置
    }
}

int main()
{
    n = read();
    m = read();
    FOR(i, 1, n)
    {
        a1[i] = read();
        pre_lsh[i] = a1[i];
        bel[i] = (i-1) / divlen + 1;
    }
    divnum = bel[n];    //分块数量
    discret();
    FOR(i, 1, divnum)
    {
        l[i] = (i-1) * divlen;          //每个板块起始位置
        r[i] = l[i] + divlen - 1;       //结束位置
    }
    l[1] = 1; r[divnum] = n;
    FOR(i, 1, divnum)
    {
        memset(cnt, 0, sizeof(cnt));
        int mama = 0, now = i;
        FOR(j, l[i], n)
        {
            cnt[a[j]]++;                //线性数过去
            mama = max(mama, cnt[a[j]]);
            if(j == r[now])
            {
                MAX[i][now] = mama;
                now++;
            }
        }
    }
    while(m--)
    {
        ql = read(); qr = read();
        lb = bel[ql], rb = bel[qr];
        ans = 0;
        if(lb == rb)        //同一分块内
        {
            FOR(i, ql, qr)
            {
                int num = a[i], siz = v[num].size(), pos = id[i];
                while(pos + ans < siz && v[num][pos+ans] <= qr)
                    ans++;
            }
        }
        else
        {
            if(lb + 1 < rb)         //先统计中间板块中预处理过的众数大小
                ans = MAX[lb+1][rb-1];
            FOR(i, ql, r[lb])       //边界板块
            {
                int num = a[i], siz = v[num].size(), pos = id[i];
                while((pos+ans) < siz && v[num][pos+ans] <= qr)
                    ans++;
            }
            FOR(i, l[rb], qr)
            {
                int num = a[i], pos = id[i];
                while(pos >= ans && v[num][pos-ans] >= ql)
                    ans++;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值