美团2024年秋招第一场笔试(小美的彩带题解)

        网上看了挺多题解了,要么上树状数组或者线段树,要么上的主席树,还有莫队加树状数组的。能用主席树这题真的就很简单了,但是笔试能现搓一个主席树我是真佩服。

        所以在这里发一份不使用任何数据结构板子的代码,不需要太多算法基础,适合大众,莫队做法(貌似还没看到有人发?)

小美的彩带

彩带是由一条长度为 n 的彩带一直无限循环得到的,彩带的每一个位置都有一个颜色,用 a[i]​ 表示。a[ i ] = a[ j ] 表示 i 位置和 j 位置颜色相同。小美每次会在当前彩带(上次剪完剩下的)从左往右或从右往左剪一段长度为 x 的彩带,她想知道她每次剪下来的彩带有多少种颜色,剪q次。

n,q<=2e5,x<=1e9

原题链接(美团2024年秋招第一场笔试)

思路:

        x大于等于n的话答案就是彩带的颜色数量,所以x的真实范围其实是x<n。那么相当于多次询问,每次询问一个长度不超过n的子彩带内颜色数量。这个模型就很熟悉了,可以将询问离线下来处理,具体的形式是b[ i ] = { L , R , idx},L是剪下来的子彩带的左端点下标,R是右端点下标,idx是第几次操作。为了方便可以把彩带扩长一倍到2n,使1<=L<=n,n+1<=R<=2*n,这样任何一段子彩带都是连续的。

        接下来就可以优雅的暴力了,我们将L分块,每一块的长度设为sqrt(n),也就是说0~sqrt(n)在一块,sqrt(n)~2*sqrt(n)在一块...,对于不在同一块的查询按照块号从小到大排序即可,块号相同的按照R从小到大排序,接下来直接暴力即可。

        分块可以将单纯暴力的复杂度从n^2降为o(n*sqrt(n))的,证明我就贴oiwiki的了,有兴趣的可以看普通莫队算法 - OI Wiki        

        还有一个问题,我们如何o1的把询问区间从 [L,R] 转移到 [L-1,R],[L+1,R],[L,R-1],[L,R+1]也就是相邻的区间,可以假设我们用map存储了 [L,R]区间内每种颜色和对应的数量,那么只需要insert和erase就知道相邻的区间的颜色数量,但是map复杂度是logN的,因此我们可以先将a数组离散化(因为a[i]范围是1e9),再通过数组模拟map,并且手动维护map.size()即可,这样复杂度就会从n*sqrt(n)*log(n)变成n*sqrt(n)+nlog(n)

下面是代码,这里的块实测取sqrt(n),2*sqrt(n),sqrt(2*n)等常见的都能焯过去,不过1.5*sqrt(2*n)貌似快些,注意在分块排序的时候的细节

(弱弱第一次发文章,尽力讲了,希望没有算法基础不会数据结构的人也能够听懂)

#include <bits/stdc++.h>
using namespace std;

#define all(x) x.begin(),x.end()

int n, q, len, m, blo;
char op; int length;
int lisan[200005];
int a[400005], ans[200005];
int mp[200005], siz;
struct stuct {
    int l, r, idx;
    bool operator<(const stuct& x) const {  //如果超时了可以试试这种
        if (l / blo != x.l / blo) return l < x.l;
        return (l / blo) & 1 ? r > x.r : r < x.r;
    }
} b[200005];
int get(int v) {    //离散的具体实现方式因人而异
    return lower_bound(lisan+1, lisan+1+m, v) - lisan;
}

int main() {
    scanf("%d %d", &n, &q);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        lisan[i] = a[i];
    }
    sort(lisan+1, lisan+1+n);
    m = unique(lisan+1, lisan+1+n)-lisan;
    --m;
    blo = sqrt(2*n)*1.5;
    for (int i = 1; i <= n; ++i) {  //离散化
        a[i] = get(a[i]);
        a[i + n] = a[i];
    }
    scanf("\n");
    int l = 1, r = 2*n, len = 0;    //维护一下l,r才知道彩带边缘的下标
    for (int i = 1; i <= q; ++i) {
        scanf("%c %d\n", &op, &length);
        if (length >= n) {
            ans[i] = m;
            length %= n;
            if (op=='L') {
                l += length;
                if (l>n)l -= n;
            }
            else {
                r -= length;
                if (r<=n)r += n;
            }
            continue;
        }
        if (op == 'L') {
            ++len;
            b[len].l = l, b[len].r = l+length-1, b[len].idx = i;
            l += length;
            if (l > n)l -= n;
        }
        else {
            ++len;
            b[len].l = r-length+1, b[len].r = r, b[len].idx = i;
            r -= length;
            if (r<=n)r += n;
        }
    }
    sort(b+1, b+1+len);
    l = 0, r = 0;
    for (int i = 1; i<=len; ++i) {
        while (r<b[i].r) {
            ++r;
            if (mp[a[r]]==0)siz++;
            mp[a[r]]++;
        }
        while (r>b[i].r) {
            mp[a[r]]--;
            if (mp[a[r]]==0)siz--;
            --r;
        }
        while (l<b[i].l) {
            mp[a[l]]--;
            if (mp[a[l]]==0)--siz;
            ++l;
        }
        while (l>b[i].l) {
            --l;
            if (mp[a[l]]==0)++siz;
            mp[a[l]]++;
        }
        ans[b[i].idx] = siz;
    }
    for (int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值