火星商店

从开这题开始,到真正A掉它竟然花了两周!主要是这题前置知识没有掌握,因此花了一周搞定了主席树专题,再花了些时间搞定了可持久化 t r i e trie trie树,再就是期间有大大小小的比赛+补题。。。反正就是两周,哈哈哈!同时,刚刚A掉它之前还发现洛谷账号变成红名了,舒服!

题意:有一排商店,每个商店都有许多商品。其中每个商店都有一种永久商品(随时都可以购买)。其次,每一天都会有一种操作,第 s s s个商店进了属性为 v v v的货物。每一天都可能有不止一个的询问,表示最近 d d d天内 [ l , r ] [l,r] [l,r]的商店内那种货物的属性与 x x x的异或值最大,输出这个最大值即可(其实题面已经很清晰了)

思路

  1. 先考虑永久商品,由于要在 [ l , r ] [l,r] [l,r]商店中选择,因此直接考虑可持久化 t r i e trie trie树的维护
  2. 而对于后面通过操作而来的新商品,我们用线段树分治解决即可。
  3. 分治第一步:现将所有的询问在线段树上分解开,记录在每个节点上( v e c t o r vector vector记录即可),当后面操作进入线段树后对这些询问依次进行处理。这个过程用线段树容易处理
  4. 分治第二步:在第一步中我们已经将询问进行了拆分,第二步我们就只需要对操作进行线段树分治。如果分治的是时间维度,那么我们首先需要按照空间维度对操作进行排序(按 s s s),这样就可以保证分治后所有的操作在空间维度和时间维度上都是前面的小于后面的
  5. 分治第三步(分治函数第一步:简称暴力):正式开始线段树分治,其实这与整体二分挺像的,但特点是需要加上一个线段树节点编号,因为我们需要找到当前分治位置有哪些之前记录过的询问。在每个节点位置,直接暴力得构建一棵新的可持久化 t r i e trie trie树(放心,毕竟是在分治上构建的,只是增加了一个 l o g log log复杂度),然后分别回答当前节点的所有询问(询问不会太多,也只是加了一个 l o g log log),并更新答案
  6. 分治第四步(分治函数第二步:简称划分):暴力处理完当前节点后,在往下一层递归时,我们需要将当前所有操作划分为时间在 m i d mid mid左边和 m i d mid mid右边的,然后用时间在 m i d mid mid左边的操作更新左儿子节点的询问,时间在 m i d mid mid右边的操作更新右儿子节点的询问
  7. 整个问题到此也就结束了,学会以后就感觉挺模板的,哈哈哈

题面描述

#include "bits/stdc++.h"
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}

const int maxn = 1e5+10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-7;

struct P{
    int s, v, t;
    bool operator < (const P &rhs) const {
        return s<rhs.s;
    }
}p[maxn], p1[maxn], p2[maxn];

struct Q{
    int l, r, x;
}q[maxn];

int n, m, cnt1, cnt2;
int ans[maxn], pos[maxn];
int sz[maxn<<6], rt[maxn<<6], son[maxn<<6][2], tot;
vector<int> node[maxn<<6];

void insert(int x, int pre, int &now) {
    int p=now=++tot;
    for(int i=17; i>=0; --i) {
        bool s=x>>i&1;
        son[p][!s]=son[pre][!s];
        p=son[p][s]=++tot, pre=son[pre][s];
        sz[p]=sz[pre]+1;
    }
}

int query(int l, int r, int x) {
    int ans=0;
    for(int i=17; i>=0; --i) {
        bool s=x>>i&1;
        if(sz[son[r][!s]]-sz[son[l][!s]]) ans|=1<<i, l=son[l][!s], r=son[r][!s];
        else l=son[l][s], r=son[r][s];
    }
    return ans;
}

void update(int i, int x, int y, int l, int r, int now) {
    if(y<l||x>r) return;
    if(x<=l&&r<=y) { node[now].push_back(i); return; }
    int m=(l+r)/2;
    if(x<=m) update(i,x,y,l,m,now<<1);
    if(y>m) update(i,x,y,m+1,r,now<<1|1);
}

void segment(int x, int y, int l, int r, int now) {
    int cnt=0;
    for(int i=x; i<=y; ++i) {
        pos[++cnt]=p[i].s;
        insert(p[i].v,rt[cnt-1],rt[cnt]);
    }
    for(int i=0; i<node[now].size(); ++i) {
        int v=node[now][i];
        int l=lower_bound(pos+1,pos+1+cnt,q[v].l)-pos-1;
        int r=upper_bound(pos+1,pos+1+cnt,q[v].r)-pos-1;
        ans[v]=max(ans[v],query(rt[l],rt[r],q[v].x));
    } //以上为暴力回答询问
    if(l==r) return; //以下为按时间划分操作
    int m=(l+r)/2, c1=0, c2=0;
    for(int i=x; i<=y; ++i)
        if(p[i].t<=m) p1[++c1]=p[i];
        else p2[++c2]=p[i];
    for(int i=1; i<=c1; ++i) p[x+i-1]=p1[i];
    for(int i=1; i<=c2; ++i) p[x+c1+i-1]=p2[i];
    segment(x,x+c1-1,l,m,now<<1);
    segment(x+c1,y,m+1,r,now<<1|1);
}

int main() {
    //ios::sync_with_stdio(false); cin.tie(0);
    n=read(), m=read();
    for(int i=1; i<=n; ++i) insert(read(),rt[i-1],rt[i]);
    for(int i=1; i<=m; ++i) {
        int f=read();
        if(!f) ++cnt1, p[cnt1]=(P){read(),read(),cnt1};
        else {
            int l=read(), r=read(), x=read(), d=read();
            ans[++cnt2]=query(rt[l-1],rt[r],x); //直接用可持久化trie树处理永久商品
            q[cnt2]=(Q){l,r,x};
            update(cnt2,max(1,cnt1-d+1),cnt1,1,m,1); //拆分询问。如果在所有询问读入结束后再进行拆分,则线段树可以是[1,cnt1]大小的,而不是[1,m]大小的,那样速度会快很多
        }
    }
    sort(p+1,p+1+cnt1); //按空间位置将操作进行排序
    segment(1,cnt1,1,m,1); //线段树分治
    for(int i=1; i<=cnt2; ++i) printf("%d\n", ans[i]);
}