2018牛客网暑假多校第一场J(树状数组+思维)

题目描述:

    有一个n个数的数列,并由q个询问,每一个询问有一个l和r,问你在区间a1—al和ar—an这两个区间中有多少个不同的数。

题目分析:

    这个题目事实上是spoj的某一题的改编,原题是求l到r区间有多少个不同的数,现在这个题是要求两个分开的区间有多少个不同的数。

    事实上这个题的做法跟求l到r区间有多少个不同的数的做法相类似。首先,因为数据范围很大,因此我们需要进行离线的操作。离线操作我们可以用莫队甚至主席树进行操作。但是这个题我们可以用树状数组进行维护。首先我们先将询问数组以有端点从小到大进行排序,之后,倘若该数为出现过,则在这位上置1;反之,倘若该数未出现过,则利用差分的思想在之前那一位置-1。

    到上面为止,这就是求l到r区间内不同的数的做法。但是在这道题来说,我们要求的是两个独立的区间。因此我们就需要发挥我们的脑洞,我们可以倍增整个数列的大小,此时对于左半个区间,倘若我们将左区间+n,右区间不变,那么我们就将原来两个不相交的区间变为了一个连续的区间。此时,我们就可以用上诉的方法求一段连续的区间的不同的数的个数。

代码:

#include <bits/stdc++.h>
#define maxn 100005*2
using namespace std;
int a[maxn];
int bit[maxn];//树状数组
int vis[maxn];
int sum[maxn];
unsigned int read()//读入挂
{
    unsigned int ret=0;
    char ch=getchar();
    while(ch>'9'||ch<'0')ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        ret=ret*10+ch-'0';
        ch=getchar();
    }
    return ret;
}
struct node{
    int l,r,id;
    bool operator <(const node & w)const{//按照右端点排序
        return r<w.r;
    }
}q[maxn];
int lowbit(int x){
    return x&(-x);
}
void add(int x,int d){
    while(x<maxn){
        bit[x]+=d;
        x+=lowbit(x);
    }
    return;
}
int get_sum(int x){
    int res=0;
    while(x){
        res+=bit[x];
        x-=lowbit(x);
    }
    return res;
}
int main()
{
    int n,Q;
    while(~scanf("%d%d",&n,&Q)){
        memset(bit,0,sizeof(bit));
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++){
            //scanf("%d",&a[i]);
            a[i]=read();
        }
        for(int i=1;i<=n;i++) a[i+n]=a[i];//建立倍增数组
        for(int i=1;i<=Q;i++){
            int tmp=read();
            q[i].r=tmp+n;//此时询问的右端点为原来的左端点+n
            q[i].l=read();//此时询问的左端点为原来右端点
            //scanf("%d%d",&q[i].l,&q[i].r);
            q[i].id=i;
        }
        sort(q+1,q+1+Q);
        int cur=1;
        for(int i=1;i<=2*n&&cur<=Q;i++){
            if(vis[a[i]]){
                add(vis[a[i]],-1);//出现过,则在前一位置-1
            }
            vis[a[i]]=i;
            add(vis[a[i]],1);//在该位置1
            while(cur<=Q&&q[cur].r<=i){
                sum[q[cur].id]=get_sum(q[cur].r)-get_sum(q[cur].l-1);
                cur++;
            }
        }
        for(int i=1;i<=Q;i++){
            printf("%d\n",sum[i]);
        }
    }
}

 

<think>嗯,用户的问题是关于如何优化树状数组和莫队算法,避免超时问题。首先,我需要回忆一下这两个数据结构的基本原理和常见的优化方法树状数组的优化,我记得有几个方向。首先,维度优化。比如二维树状数组,如果每个节点也是一个树状数组,这样的结构在更新和查询时的时间复杂度是O(log²n),当数据量大时可能会比较慢。或许可以尝试降维,比如将二维问题转换为一维,或者使用哈希表来压缩空间,减少不必要的节点。另外,离散化坐标也是一个常见的方法,特别是当坐标范围很大但实际使用到的点稀疏时,这样可以减少内存占用和访问时间。 然后是操作优化。比如批量处理个查询或更新操作,利用缓存机制,减少重复计算。还有位运算优化,比如用lowbit的位运算代替乘法或除法,虽然这可能编译器已经优化了,但手动确保代码中使用位运算还是有帮助的。 接下来是莫队算法的优化。莫队本身的时间复杂度是O(n√n),但实际应用中常数优化也很重要。分块大小的调整,通常取√n,但有时候取n/√q(q是查询次数)会更优,这需要根据具体情况调整。另外,排序查询的顺序,使用奇偶块排序,可以减少指针移动的总距离,从而减少常数时间。 离线处理方面,莫队需要所有查询离线处理,所以预处理和合理组织查询顺序是关键。还有常数优化,比如用更快的输入输出方法,减少函数调用,使用数组代替STL容器等。 另外,用户提到了二维树状数组和树套树的情况,可能涉及到高维问题。例如引用[1]中的二维树状数组,每个节点也是一个树状数组,这种情况下优化可能需要结合分块或者离散化。而莫队算法在处理区间查询时,分块策略和排序方式都会影响效率,需要根据数据特点调整。 用户可能的应用场景比如在竞赛编程中处理大数据量的查询,或者在文件管理中需要高效的数据结构。例如引用[3]中的均分数据问题,可能涉及到次查询和更新,这时候优化树状数组和莫队算法就显得尤为重要。 需要注意的是,用户的问题可能还涉及到具体实现的细节,比如代码中的循环结构、内存访问模式等。例如在树状数组中,循环展开或者使用更紧凑的数据结构来减少缓存未命中。在莫队算法中,避免频繁的内存分配和释放,使用预分配的数组。 最后,生成相关问题时要覆盖这两个数据结构的优化方法、应用场景以及与其他算法的结合使用。比如询问如何选择分块大小,或者如何结合离散化技术优化树状数组,或者莫队算法在动态问题中的变种。</think>### 树状数组与莫队算法的优化方法 #### 一、树状数组优化 1. **维度压缩** 对高维树状数组进行降维处理。例如,二维问题可离散化坐标后通过哈希映射到一维,减少空间和时间复杂度[^1]。若问题允许,使用缀和差分代替树状数组。 2. **离散化坐标** 当坐标范围过大但实际稀疏时,对坐标离散化以减少节点数量。例如,将坐标映射为$1,2,\dots,k$,树状数组大小从$10^9$压缩到$10^5$。 3. **批量操作优化** 合并次更新或查询操作,减少重复计算。例如,离线处理操作序列,利用缓存机制降低时间复杂度。 4. **位运算加速** 确保`lowbit`计算使用位运算: $$ \text{lowbit}(x) = x \& (-x) $$ 同时避免循环中的冗余判断,例如预计算索引偏移量。 #### 二、莫队算法优化 1. **分块大小调整** 分块大小通常取$\sqrt{n}$,但若查询次数$q$较大,可调整为: $$ \text{块大小} = \frac{n}{\sqrt{q}} $$ 实验调整以平衡移动次数和块内排序代价。 2. **奇偶块排序** 对查询按左端点所在块排序,若块为奇数则按右端点升序,偶数块按右端点降序。减少指针跳跃次数,优化常数因子[^2]。 3. **离线处理与预处理** 预处理所有查询,按优化后的顺序处理,避免重复计算。结合缀和或预计算区间特征(如频次分布)加速统计。 4. **常数优化** - 使用数组代替`unordered_map`统计频次。 - 使用快速读入/输出函数(如`fread`)。 - 减少函数调用,内联关键代码。 #### 三、综合优化示例 **场景**:二维区间求和(如引用[1]的二维树状数组) - **步骤1**:离散化$x,y$坐标,压缩空间至实际范围。 - **步骤2**:若操作可离线,转化为一维问题(如扫描线+莫队)。 - **步骤3**:莫队处理时,调整块大小并使用奇偶排序,减少指针移动。 ```cpp // 莫队奇偶排序示例 bool cmp(const Query& a, const Query& b) { if (a.block != b.block) return a.block < b.block; return (a.block & 1) ? (a.r < b.r) : (a.r > b.r); } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值