【题解】FJOI2015火星商店问题

  好几天之前做的题目了,一直想写一下博客也没腾出时间来,今天赶紧把坑给填上呼呼呼~

  这道题首先如果只考虑每个商店中没有时间限制的物品时,我们只需要使用一棵可持久化trie树来维护区间内的异或最大值即可,这样我们可以把两部分的问题分离开来。

  之后我们再考虑有时间限制与编号限制的情况下,该怎样做?无脑做法线段树套trie,直接在对应的区间trie树上贪心,如果该条边的最后更新时间在允许的范围内,说明可以走这条边。虽然这样也可以卡过去(主要在于卡空间),但我们来介绍一种更加妙妙的线段树分治做法。其实我感觉在这题的体现上就是离线版树套树?机房大佬貌似不认可,但我确实是这样觉得哒。

  我们考虑在线椴树上的一次区间查询,实际上是将查询的区间分成了log个已经处理好的小区间,分别查询之后再合并达到最终的答案。那么我们可以模拟这个在线段树上分裂区间的操作:

  1.如果当前处理的区间(l,r)完全包含于询问区间,我们将这个询问的区间在这一层进行处理。(这样的询问均不再下传,跳过后续步骤)

  2.如果询问的区间有涉及(l,mid)的范围,递归左区间处理。

  3.如果询问的区间有涉及(mid+1,r)的范围,递归右区间处理。

  (本题分治的区间表示时间的范围,可持久化trie负责处理编号的限制)。

  这样的复杂度是多少?我们可以分处理trie树与查询的两个方面来分析。处理trie树时,我们插入一个值是log的复杂度,而包含一个修改的总分治区间数也是log个,所以是\(O(nlog^{2}n)\)的复杂度。查询也是log的复杂度,一个查询最多被分别查询 log 次。所以复杂度依然是 \(O(log^{2}n)\) 的。

#include <bits/stdc++.h>
using namespace std;
#define maxn 1000000
int n, m, sz, qt, nt, tot, cnt, timer;
int ans[maxn], sum[maxn * 5], id[maxn], q[maxn];
int bit[30], ch[maxn * 5][2], num[maxn], root[maxn];

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

struct ques
{
    int l, r, s, t, x;
}Q[maxn];

struct modify
{
    int id, val, t;
    friend bool operator <(const modify& a, const modify& b)
    { return a.id < b.id; }
}P[maxn], ql[maxn], qr[maxn];

void Ins(int now, int last, int x, int val)
{
    if(x < 0) return;
    bool t = (bit[x] & val);
    ch[now][!t] = ch[last][!t]; ch[now][t] = ++ sz;
    sum[ch[now][t]] = sum[ch[last][t]] + 1;
    Ins(ch[now][t], ch[last][t], x - 1, val);
}

int Query(int l, int r, int x, int val)
{
    if(l > r || x < 0) return 0;
    int ans = 0; bool t = val & bit[x];
    if(sum[ch[r][!t]] - sum[ch[l][!t]])
        ans = (bit[x] + Query(ch[l][!t], ch[r][!t], x - 1, val));
    else ans = Query(ch[l][t], ch[r][t], x - 1, val);
    return ans;
}

int Lower(int v)
{
    int l = 1, r = nt, ret = 0;
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(num[mid] <= v) ret = mid, l = mid + 1;
        else r = mid - 1;
    }
    return ret;
}

void Work(int l, int r)
{
    sz = 0, nt = 0;
    for(int i = l; i <= r; i ++)
    {
        nt ++; root[nt] = ++ sz;
        Ins(root[nt], root[nt - 1], 17, P[i].val);
        num[nt] = P[i].id;
    }
    for(int i = 1; i <= qt; i ++)
    {
        int l = Lower(Q[q[i]].l - 1), r = Lower(Q[q[i]].r);
        ans[q[i]] = max(ans[q[i]], Query(root[l], root[r], 17, Q[q[i]].x));
    }
}

void Divide(int l, int r, int L, int R, int pres)
{
    if(l > r || !pres) return;
    int mid = (L + R) >> 1; qt = 0;
    for(int i = 1; i <= pres; i ++)
        if(Q[id[i]].s <= L && R <= Q[id[i]].t) q[++ qt] = id[i];
    Work(l, r); if(L == R) return;
    int ll = 0, rr = 0;
    for(int i = l; i <= r; i ++)
    {
        if(P[i].t <= mid) ql[++ ll] = P[i];
        else qr[++ rr] = P[i];
    }
    for(int i = 1; i <= ll; i ++) P[i + l - 1] = ql[i];
    for(int i = 1; i <= rr; i ++) P[i + l + ll - 1] = qr[i]; 

    int ind = 0;
    for(int i = 1; i <= pres; i ++)
    {
        if(Q[id[i]].s <= L && R <= Q[id[i]].t) continue;
        if(Q[id[i]].s <= mid) swap(id[i], id[++ ind]);
    }
    Divide(l, l + ll - 1, L, mid, ind);
    ind = 0;
    for(int i = 1; i <= pres; i ++)
    {
        if(Q[id[i]].s <= L && R <= Q[id[i]].t) continue;
        if(Q[id[i]].t > mid) swap(id[i], id[++ ind]);
    }
    Divide(l + ll, r, mid + 1, R, ind);
}

int main()
{
    bit[0] = 1; for(int i = 1; i <= 20; i ++) bit[i] = bit[i - 1] << 1;
    n = read(), m = read();
    for(int i = 1; i <= n; i ++) root[i] = ++ sz, Ins(root[i], root[i - 1], 17, read());
    for(int i = 1; i <= m; i ++)
    {
        int opt = read();
        if(!opt)
        {
            ++ timer; P[++ tot].id = read();
            P[tot].val = read(); P[tot].t = timer;
        }
        else
        {
            Q[++ cnt].l = read(); Q[cnt].r = read();
            Q[cnt].x = read(); int d = read();
            Q[cnt].s = max(timer - d, 0) + 1; Q[cnt].t = timer;
            ans[cnt] = Query(root[Q[cnt].l - 1], root[Q[cnt].r], 17, Q[cnt].x);
        }
    }
    sort(P + 1, P + 1 + tot);
    for(int i = 1; i <= cnt; i ++) id[i] = i;
    Divide(1, tot, 1, timer, cnt);
    for(int i = 1; i <= cnt; i ++) printf("%d\n", ans[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/twilight-sx/p/9827600.html

时间: 2024-10-02 05:35:03

【题解】FJOI2015火星商店问题的相关文章

[FJOI2015]火星商店问题(分治+可持久化)

题目描述 火星上的一条商业街里按照商店的编号1,2 ,-,n ,依次排列着n个商店.商店里出售的琳琅满目的商品中,每种商品都用一个非负整数val来标价.每个商店每天都有可能进一些新商品,其标价可能与已有商品相同. 火星人在这条商业街购物时,通常会逛这条商业街某一段路上的所有商店,譬如说商店编号在区间[L,R]中的商店,从中挑选1件自己最喜欢的商品.每个火星人对商品的喜好标准各不相同.通常每个火星人都有一个自己的喜好密码x.对每种标价为val的商品,喜好密码为x的火星人对这种商品的喜好程度与val

洛谷 P4585 [FJOI2015]火星商店问题

(勿看,仅作笔记) bzoj权限题... https://www.luogu.org/problemnew/show/P4585 对于特殊商品,直接可持久化trie处理一下即可 剩下的,想了一段时间cdq,但是没想出来...应该是不行的 事实上,如果询问的不是最大值,而是一些满足[l,r]的答案等于[1,r]的答案-[1,l-1]的答案的东西,那么的确可以每个询问拆成两个直接cdq... 但是这题就不能..不过可以线段树分治,这是基于[l,r]的答案可以被分成多个线段树上区间(这些区间的并等于[

bzoj 4137 [FJOI2015]火星商店问题——线段树分治+可持久化01trie树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4137 关于可持久化01trie树:https://www.cnblogs.com/LadyLex/p/7281110.html 看了看它的两道例题,就没写. 特殊商品可以直接用可持久化trie做. 其他部分用线段树分治.修改是单点的,询问是区间,原来想的是把询问区间定位后有 mlogn 个,在线段树的每个叶子上贡献一番:结果TLE了,因为若是在叶子处贡献,一个询问就要做 r-l+1 次.

bzoj4137 [FJOI2015]火星商店问题

比较容易想到的做法是线段树套字典树,修改操作时在字典树上经过的节点维护一个最近被访问过的时间,这样询问操作只经过满足时间条件的节点,时间复杂度O(NlogN^2)但是因为线段树每个节点都要套个字典树,这样的话空间是不够的,不过由于可以离线处理,我们可以先把每个修改和询问操作所访问的线段树节点保存下来,然后一个个节点去做,这样的话空间复杂度就可以保证了. 代码 1 #include<cstdio> 2 #include<set> 3 #include<vector> 4

luogu P4585 [FJOI2015]火星商店问题

luogu 异或最大值显然可以01trie贪心选取 然后涉及到时间区间内元素贡献,可以把trie可持久化 还涉及区间内集合贡献,那么我们搞个线段树,把操作放到对应节点到根的链上,把询问放到对应区间的log个节点上,然后对着每个线段树节点计算贡献,算完后清空trie,空间\(O(nlogn)\),时间两个\(log\) 还有些和时间无关的物品,单独处理即可 #include<bits/stdc++.h> #define LL long long #define uLL unsigned long

线段树分治

2014徐寅展论文<线段树在一类分治问题上的应用>读后感. 线段树分治 线段树分治其实就是有撤销操作的时间分治. 题目让你维护一些信息,每次可以询问,可以执行一种操作,也可以将之前的某个这种操作撤回. 操作容易维护,但撤回操作不容易维护. 需要将操作,询问都离线下来.将时间轴画出来,那么每个操作只在时间轴上的一个区间内生效. 用线段树给这个区间打上这个操作的标记,维护信息. TJOI2018 数学计算 小豆现在有一个数x,初始值为1. 小豆有Q次操作,操作有两种类型: m: x = x * m

YCB 的暑期计划

前言 YCB现在很弱(TAT) 暑假有一个月,赶快狂补一下. 大概的计划如下: 首先前期会以数据结构为主,毕竟代码能力太弱,涉及内容:线段树分治.二进制分组.KD-Tree. 等数据结构做到没有智商的时候加入一波数论,内容为 杜教筛.min_25筛. 然后中途小清新一下,做一些 组合博弈与构造题. 接着继续练代码能力,顺便学一些神奇的暴力:启发式合并.dsu on tree . 然后图论也忘的差不多了,就回过头去学点新东西,大概会有spfa判负环.0/1分数规划.差分约束. 估计这个时候也没有什

2019-9-11做题记录

1.[luogu4921]情侣,给我烧了: 我们这里只讨论直接算代替容斥的做法. $n$个情侣,恰好有$k$个不和谐,枚举是哪$k$个,$C_n^k$.坐在哪些位置上,$C_n^k$.确定先后关系(为下面算$f$埋下伏笔),$2^n$.确定顺序,$k! (n-k)!$. 剩下还要求$i$对情侣,已经指定了顺序,和彼此先后关系,求任意一对都不坐在一排上的方案数.也就是说对于任意一种方案,我们都可以唯一确定它的顺序和彼此的先后关系,因为我已经乘过了.考虑顺序和彼此先后关系怎么体现:也就是说,对于“$

[网络流专练6][线性规划与网络流剩余部分题解]

orz"orzGEOTCBRL" 6:lis 给定正整数序列x1 ,…… , xn. (1)计算其最长递增子序列的长度s. (2)计算从给定的序列中最多可取出多少个长度为s的递增子序列. (3)如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长 度为s的递增子序列.  看到题目就666了,前两问不是经典dp?因为序列是上升的,所以如果在x1前面加一个x1,个数显然多了f[x2][xn][s-1],或者xn后面加一个xn, 一样的……然后发现题意理解错T_T “多