洛谷 P3987 我永远喜欢珂朵莉~(Splay+BIT+无限卡常)

题目背景

http://sukasuka-anime.com/

虽然2018已经到来很久了,但是就用它作为2018年的第一篇博客,借此表达我对珂学的热爱吧!


题面

https://www.luogu.org/problemnew/show/3987


解题思路

好久好久之前做的一道题了,貌似是洛谷某月赛的题目。。

解题思路什么的早就不记得啦,只能瞎BB了。。

算了,贴个官方标解吧!

可以发现一个数最多被/log次(无视掉1和0的情况)

瓶颈在于如何找出所有该被/的数而不在于如何维护

500000以内的有最多约数的数有200个约数

然后可以用平衡树来维护

把每个i插入ai的所有约数对应的平衡树里面

每次区间[l,r]中x的倍数/x的时候

则在第x个平衡树里面把区间[l,r]截出来然后DFS这个子树

边DFS边删掉里面所有ai/x后不为x倍数的下标i

平衡树访问连续size个数的复杂度为logn+size的

总复杂度O( nd + mlog^2n ) , 空间O( nd ),d为值域内最大约数个数,即200

没错,这是一道很适合新手做的Splay入门题,因为它不仅综合了数学和BIT等其他的知识,而且还具有优秀的数据结构题应有的特点:卡常!!

一开始疯狂T的我,加了几个玄学优化就过了。
具体就是Splay开m棵就够了,Build的时候不要一个个插入,直接线性建树,然后加一堆底层优化就好了!

最后勉勉强强卡过了!

这里写图片描述

其余一堆细节我都忘了,都是些模拟的操作。


代码

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <vector> 
#define maxn 100010
#define maxN 500010
#define maxD 210
#define INF 0x7FFFFFFF

using namespace std;

typedef long long LL;

int n, m;
int cnt;
int Del[maxn];
bool exist[maxN];
LL a[maxn], BIT[maxn];
vector <int> Num[maxN];

struct Data{
    int type, l, r, x;
}op[maxn];

struct Tnode{
    Tnode *son[2], *fa;
    int id;
    inline int Get_d(){return fa->son[1] == this;}
    inline void Connect(Tnode *now, int d){(son[d] = now)->fa = this;}
}Node[maxn*maxD+maxN*2], *Root[maxN];

inline int lowbit(int x){
    return x & (-x);
}

inline void Add(int x, LL v){
    for(register int i = x; i <= n; i += lowbit(i))  BIT[i] += v;
}

inline LL Sum(int x){
    LL res = 0LL;
    for(register int i = x; i; i -= lowbit(i))  res += BIT[i];
    return res;
}

inline Tnode *NewTnode(int id){
    Node[cnt].son[0] = Node[cnt].son[1] = Node[cnt].fa = NULL;
    Node[cnt].id = id;
    return Node+cnt++;
}

inline void Zig(Tnode *now, Tnode *&tag){
    Tnode *last = now->fa;
    int d = now->Get_d();
    if(now->son[!d])  last->Connect(now->son[!d], d);
    else  last->son[d] = NULL;

    if(last == tag){
        now->fa = tag->fa;
        tag = now;
    }
    else  last->fa->Connect(now, last->Get_d());

    now->Connect(last, !d);
}

inline void Splay(Tnode *now, Tnode *&tag){
    Tnode *last;
    while(now != tag){
        last = now->fa;
        if(last != tag)  (last->Get_d() ^ now->Get_d()) ? Zig(now, tag) : Zig(last, tag);
        Zig(now, tag);
    }
}

int FindPre(Tnode *now, int x, int pre){
    if(!now)  return pre;
    if(now->id < x)  return FindPre(now->son[1], x, now->id);
    else  return FindPre(now->son[0], x, pre);
}

int FindSuc(Tnode *now, int x, int suc){
    if(!now)  return suc;
    if(now->id > x)  return FindSuc(now->son[0], x, now->id);
    else  return FindSuc(now->son[1], x, suc);
}

void Work(Tnode *now, int x, Tnode *&tag){
    if(now->id == x)  Splay(now, tag);
    else if(now->id < x)  Work(now->son[1], x, tag);
    else  Work(now->son[0], x, tag);
}

Tnode *Build(int L, int R, int x, Tnode *last){
    if(L > R)  return NULL;
    int mid = (L + R) >> 1;
    Tnode *now = NewTnode(Num[x][mid]);
    now->fa = last;
    now->son[0] = Build(L, mid-1, x, now);
    now->son[1] = Build(mid+1, R, x, now);
    return now;
}

void Delete(int x, int v){
    int low = FindPre(Root[x], v, -INF);
    int high = FindSuc(Root[x], v, INF);
    Work(Root[x], low, Root[x]);
    Work(Root[x], high, Root[x]->son[1]); 

    Root[x]->son[1]->son[0] = NULL;
}

void Dfs(Tnode *now, int x){
    if(!now)  return;
    Dfs(now->son[0], x);
    Dfs(now->son[1], x);
    LL temp = a[now->id];
    if(temp % LL(x) != 0){
        Del[++Del[0]] = now->id;
        return;
    }
    temp /= (LL)x;
    Add(now->id, temp-a[now->id]);
    a[now->id] = temp;
}

void Update(int l, int r, int x){
    if(x == 1)  return;
    int low = FindPre(Root[x], l, -INF);
    int high = FindSuc(Root[x], r, INF);
    Work(Root[x], low, Root[x]);
    Work(Root[x], high, Root[x]->son[1]);

    Del[0] = 0;
    Dfs(Root[x]->son[1]->son[0], x);

    for(register int i = 1; i <= Del[0]; ++i)  Delete(x, Del[i]);
}

inline LL Query(int l, int r){
    return Sum(r) - Sum(l-1);
}

int Read(){
    int x = 0;  char ch = getchar();
    while(ch < '0' || ch > '9')  ch = getchar();
    while(ch >= '0' && ch <= '9'){x = (x << 3) + (x << 1) + ch - '0';  ch = getchar();}
    return x;
}

void Print(LL x){
    if(x > 9)  Print(x / 10);
    putchar(x % 10 + '0');
}

int main(){

    n = Read();  m = Read();

    for(register int i = 1; i <= n; ++i){  
        a[i] = Read();
        Add(i, a[i]);
    }

    for(register int i = 1; i <= m; ++i){
        op[i].type = Read();  op[i].l = Read();  op[i].r = Read();
        if(op[i].type == 1){
            op[i].x = Read();
            int X = op[i].x;
            if(X ^ 1 && !exist[X]){  
                exist[X] = true;  
                Num[X].push_back(-INF);
            }
        }
    }

    for(register int i = 1; i <= n; ++i){
        for(register int j = 1; j * j <= a[i]; ++j){
            if(a[i] % j)  continue;
            int d1 = j, d2 = a[i] / j;
            if(exist[d1])  Num[d1].push_back(i);
            if(d1 != d2 && exist[d2])  Num[d2].push_back(i);
        }
    }

    for(register int i = 1; i <= m; ++i){
        if(op[i].type ^ 1)  continue;
        int X = op[i].x;
        if(exist[X]){
            Num[X].push_back(INF);
            Root[X] = Build(0, Num[X].size()-1, X, NULL);
            exist[X] = false;
        }
    }

    for(register int i = 1; i <= m; ++i){
        if(op[i].type == 1)  Update(op[i].l, op[i].r, op[i].x);
        else{  
            Print(Query(op[i].l, op[i].r));
            putchar('\n');
        }
    }

    return 0;
}

这里写图片描述

<think>嗯,用户想了解如何用分块方法解决洛谷P2042这个维护数列的问题。首先,我需要回忆一下分块算法的基本概念。分块通是将数据分成多个块,每个块单独处理,平衡查询和修改的时间复杂度。不过,题目是NOI2005的,可能比较复杂,因为维护数列通涉及插入、删除、区间修改、翻转、求和、最大子段和等操作。分块处理这些操作可能比较麻烦,尤其是动态的插入和删除,会导致块的大小变化,影响效率。 首先,我得确认分块是否适合这个问题。一般来说,分块的时间复杂度是O(n√n),对于大数可能还行,但像这种NOI题目可能数据量较大,分块可能不够高效,不过用户明确问分块方法,所以需要找到如何结构设计。 维护数列的典型操作包括:在某个位置插入一段数列,删除某个区间,区间覆盖,区间翻转,区间求和,求最大子段和。分块的话,每个块需要维护这些操作的信息。比如每个块需要记录翻转标记、覆盖标记,以及各种区间和、最大子段和等。 接下来,分块的结构设计。每个块可以存储一个数组,同时维护块内的总和、最大前缀和、最大后缀和、最大子段和,以及翻转和覆盖的懒标记。插入和删除操作需要考虑块的分裂和合并,这可能比较复杂,因为频繁的插入删除会导致块的大小不均,影响效率。所以可能需要设置块的大小阈值,比如当块的大小超过2√n时分裂,小于√n/2时合并相邻块。 处理翻转操作时,每个块维护一个翻转标记,当需要翻转区间时,对覆盖的完整块标记翻转,并处理部分块。同样,覆盖操作需要懒标记,当块被完全覆盖时,直接更新块内的值,并记录覆盖值。 最大子段和的计算需要每个块维护内部的最大子段和、最大前缀和、最大后缀和,这样在查询时,可以将多个块的信息合并起来得到整体的最大子段和。这可能比较复杂,需要考虑块之间的前后缀如何组合。 插入和删除操作需要找到对应的位置所在的块,如果插入导致块过大,就分裂成两个块。删除时如果块变得过小,可能需要合并相邻块。这些操作需要维护块之间的链表结构,方便快速插入和删除块。 举个例子,当要在位置pos插入一段数列,首先找到pos所在的块,如果块的大小加上插入元素后超过阈值,就分裂该块为两个,然后将插入的元素作为新块插入到链表中。这样插入的时间复杂度是O(√n),因为分裂和插入块的时间与块的大小有关。 对于翻转和覆盖操作,应用懒标记是关键,这样可以延迟实际的操作,直到需要访问块的具体内容时才进行实际的处理。例如,当需要访问一个带有翻转标记的块时,先执行翻转操作,并清除标记。 不过分块在处理这些动态操作时,尤其是频繁的插入和删除,可能效率不如平衡树结构(如Splay或Treap),但分块实现起来可能相对简单一些。需要权衡实现复杂度和效率。 总结一下,分块实现的关键点包括: 1. 分块的结构设计,每个块维护必要的信息和懒标记。 2. 插入和删除时的块分裂与合并策略。 3. 懒标记的处理,如翻转和覆盖,何时下传。 4. 查询操作时如何合并各个块的信息,如最大子段和。 可能遇到的问题包括块的大小管理不当导致性能下降,懒标记处理错误导致数据不一致,以及在合并块信息时逻辑错误。需要详细设计每个操作的步骤,并处理各种边界情况,比如操作区间跨多个块,或者在块的开头或结尾的部分操作。 最后,参考用户提供的引用,尤其是引用[1]提到递归图的理解,可能分块的递归处理不太相关,但引用[2]提到洛谷题目,可能需要参考类似题解中的分块实现方法。不过用户提供的引用可能没有直接相关的内容,所以需要依靠已有的分块知识来构建解决方案。</think>### 分块方法实现洛谷 P2042 维护数列问题 #### 分块结构设计 1. **块内信息**: - 数据存储:每个块存储$size \leq 2\sqrt{n}$个元素 - 维护以下字段: ```text sum: 区间和 lmax: 最大前缀和 rmax: 最大后缀和 tmax: 最大子段和 rev: 翻转标记(0/1) cov: 覆盖标记(特殊值表示未覆盖) ``` 2. **分块逻辑**: - 块间通过链表或数组连接 - 插入/删除时动态调整块大小,触发分裂或合并(例如块大小超过$2\sqrt{n}$时分裂,相邻块总大小低于$\sqrt{n}$时合并) #### 核心操作实现 **1. 区间翻转(REVERSE)**: - 对完整覆盖的块:翻转标记`rev ^= 1`,交换`lmax`与`rmax` - 对部分覆盖的块:下传标记后暴力翻转元素 时间复杂度:$O(\sqrt{n})$ **2. 区间覆盖(MAKE-SAME)**: - 对完整覆盖的块:更新`cov`标记,直接计算`sum`,`lmax`,`rmax`,`tmax` - 对部分覆盖的块:下传标记后暴力修改 时间复杂度:$O(\sqrt{n})$ **3. 最大子段和(MAX-SUM)**: - 合并相邻块的`rmax`和`lmax`,遍历所有块维护全局最大值 时间复杂度:$O(\sqrt{n})$ **4. 插入(INSERT)**: - 定位插入位置所在块,若插入后块大小超过阈值则分裂 时间复杂度:$O(\sqrt{n} + k/\sqrt{n})$ **5. 删除(DELETE)**: - 移除目标区间涉及的所有块,合并相邻小块 时间复杂度:$O(\sqrt{n} + k/\sqrt{n})$ #### 代码框架示例(关键部分) ```python class Block: def __init__(self): self.data = [] self.sum = 0 self.lmax = self.rmax = self.tmax = -inf self.rev = 0 self.cov = None def push_down(self): # 下传覆盖/翻转标记到数据层 if self.cov is not None: self.data = [self.cov] * len(self.data) if self.rev: self.data.reverse() self.rev = 0 self.cov = None def update_info(self): # 重新计算sum/lmax/rmax/tmax pass class BlockList: def __init__(self): self.blocks = [] def split(self, idx): # 分裂过大的块 pass def merge(self): # 合并过小的相邻块 pass ``` #### 复杂度分析 | 操作类型 | 时间复杂度 | |----------------|---------------------| | 插入/删除 | $O(\sqrt{n} + k)$ | | 翻转/覆盖/求和 | $O(\sqrt{n})$ | | 最大子段和 | $O(\sqrt{n})$ | #### 注意事项 1. **标记下传策略**:在访问块内数据前必须下传所有标记 2. **块大小平衡**:通过动态分裂/合并保证$size \in [\sqrt{n}/2, 2\sqrt{n}]$ 3. **边界处理**:特别注意区间跨多个块时的部分覆盖情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值