[BZOJ]3489 A simple rmq problem 主席树套树

3489: A simple rmq problem

Time Limit: 40 Sec Memory Limit: 600 MB
Submit: 2074 Solved: 708
[Submit][Status][Discuss]
Description

因为是OJ上的题,就简单点好了。给出一个长度为n的序列,给出M个询问:在[l,r]之间找到一个在这个区间里只出现过一次的数,并且要求找的这个数尽可能大。如果找不到这样的数,则直接输出0。我会采取一些措施强制在线。

Input

第一行为两个整数N,M。M是询问数,N是序列的长度(N<=100000,M<=200000)
第二行为N个整数,描述这个序列{ai},其中所有1<=ai<=N
再下面M行,每行两个整数x,y,
询问区间[l,r]由下列规则产生(OIER都知道是怎样的吧>_<):
l=min((x+lastans)mod n+1,(y+lastans)mod n+1);
r=max((x+lastans)mod n+1,(y+lastans)mod n+1);
Lastans表示上一个询问的答案,一开始lastans为0
Output

一共M行,每行给出每个询问的答案。
Sample Input

10 10

6 4 9 10 9 10 9 4 10 4

3 8

10 1

3 4

9 4

8 1

7 8

2 9

1 1

7 3

9 9

Sample Output

4

10

10

0

0

10

0

4

0

4

HINT

注意出题人为了方便,input的第二行最后多了个空格。

2015.6.24新加数据一组,2016.7.9放至40S,600M,但未重测

Source

by zhzqkkk

题解

  原来的编辑模式字体有毒改不过来啊... 这下粘题面就不能顺便粘走超链接. 只能用markdown了, 不过谁能告诉我markdown怎么行首空两格... Upd:现在会了.
  不会K-D Tree怎么办… 不过话说K-D Tree不会被卡吗?
  一开始在openjudge的cdqz小组里数据结构中看到此题, 不过那道题可以离线. 当时想了很久… 一开始觉得这道题非常的naive, 主席树搞一下就行了! 发现唯一出现实际上是一个三维关系.
  三维关系分别是如果a[i]要对L到R的询问构成贡献, 那么pre[a[i]] < L, nxt[a[i]] > R, L <= i <= R. 所以说就可以转化为三维空间, 用K-D Tree即可, 只是本蒟蒻不会…. 但是K-D Tree很难卡但是也应该是能被卡的吧.

  冥思苦想YY了一下树套树, 写了一发.
  但是从来没学过没做过树套树啊!!一上来还想了个主席树套树, 真的是作死. 当时想着时间空间都是nlog^2n的, 十万能过, 没有想过实现的困难就写了. 搞了半个下午… (突然想起之前没有学过带修改莫队和树上莫队直接去做糖果公园… 自己zuo啊… 那道200s的题有一次T了, 卡了评测两页gg).

  我们按pre排一下序, 这样sort可以消去一位.
  用主席树对pre值序列维护. root[i]保存了所有pre在i位置及之前的所有数字的信息(先不管是什么信息, 反正满足原序列里pre在i之前的就会被记录进去). 然后对于root[i], L-R区间代表的是整棵root[i]保存数字中满足nxt在L-R的数字的信息. 这样就满足pre和nxt的限制了.

  然后对于第三维, 我们就对于主席树的每个节点开一棵线段树, 这棵线段树的L-R保存的就是数字本身位置在L-R的信息, 这个信息就是这个数字的值. 这样就满足第三维了.
  这样我们对于L-R的查询, 在root[L-1]里查, 满足查询的数字的pre < L. 再进入到root[L-1]的R+1-n区间中, 这样就满足了nxt>R(主席树即外层的树是以nxt为下标的). 然后在这个区间里的线段所套的线段树查L-R的保存的信息即数字本身值得最大值. 这样三维就满足了.

  分析一下空间. 主席树动态开链, 构建主席树是每到一个位置i就会开一个log的链, 然后对于这条链上的每个节点开的线段树也动态开一条log链来保存i的信息, 这样就是log^2n的空间, 因为有n个位置, 所以是nlog^2n的空间.

  分析一下时间. 每次查询R+1-n时间包含log个节点, 每个节点还要进入所套的线段树再查L-R的最大值, 所以每次查询nlog^2n的.

  所以说可以过了~ 做出来还是成就满满. 但是openjudge的cdqz的那道练习过不去, 因为那道题n是300000而且空间只有512mb, bzoj有600mb… 于是乎MLE了.

  但是cdqz那道题可以离线啊…前辈过的都写得K-D Tree, 但是想到可以离线转化成二维平面上的矩形单点查询, 扫描线nlog完美解决... 日后搞算几练扫描线的时候再去A那道题吧, 说不定艹榜... Upd:发现还是要nlog^n?

  树套树代码不长, 常数有点大…(原来的代码风格的话不加读优可能就80多行)

#include<stdio.h>
#include<algorithm>
#define Acce register int
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 3e7 + 3e6;
int n, m, l, r, ans, las, last[maxn];
inline const int read()
{
    Acce 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;
}
struct point
{
    int v, pre, nxt, id;
    friend bool operator < (point x, point y)
    { return x.pre < y.pre; }
}a[maxn];
struct node
{
    int cmax;
    node *ls, *rs, *rt;
}pool[maxm], *root[maxn], *null, *tail = pool;
inline void init()
{
    null = ++ tail;
    null -> cmax = 0;
    null -> ls = null -> rs = null -> rt = null;
}
inline node* newnode()
{
    node* bt = ++ tail;
    bt -> ls = bt -> rs = bt -> rt = null;
    bt -> cmax = 0;
    return bt;
}
void modify(node* bt, node* pre, int lf, int rg, int pos, int val)
{   
    bt -> cmax = max(max(bt -> cmax, pre -> cmax), val);
    if (lf == rg) return; 
    int mid = (lf + rg) >> 1;
    if (pos <= mid)
    {
        if(bt -> ls == null) bt -> ls = newnode();
        if(bt -> rs == null) bt -> rs = pre -> rs;
        modify(bt -> ls, pre -> ls, lf, mid, pos, val);
    }   else
    {
        if(bt -> rs == null) bt -> rs = newnode();
        if(bt -> ls == null) bt -> ls = pre -> ls;
        modify(bt -> rs, pre -> rs, mid + 1, rg, pos, val);
    }
}
void insert(node* bt, node* pre, int lf, int rg, point x)
{
    if(bt -> rt == null) bt -> rt = newnode();
    modify(bt -> rt, pre -> rt, 1, n, x.id, x.v);
    if(lf == rg) return;
    int mid = (lf + rg) >> 1;
    if (x.nxt <= mid)
    {
        if (bt -> ls == null) bt -> ls = newnode();
        if (bt -> rs == null) bt -> rs = pre -> rs;
        insert(bt -> ls, pre -> ls, lf, mid, x);
    }   else
    {
        if (bt -> rs == null) bt -> rs = newnode();
        if (bt -> ls == null) bt -> ls = pre -> ls;
        insert(bt -> rs, pre -> rs, mid + 1, rg, x);
    }
}
int query_cmax(node *bt, int lf, int rg, int L, int R)
{
    if (L <= lf && rg <= R) return bt -> cmax;
    int rt1 = 0, rt2 = 0, mid = (lf + rg) >> 1;
    if (L <= mid) rt1 = query_cmax(bt -> ls, lf, mid, L, R);
    if (R > mid)  rt2 = query_cmax(bt -> rs, mid + 1, rg, L, R);
    return max(rt1, rt2);
}
int query(node* bt, int lf, int rg, int L, int R)
{
    if (L <= lf && rg <= R)
        return query_cmax(bt -> rt, 1, n, l - 1, r - 1);
    int rt1 = 0, rt2 = 0, mid = (lf + rg) >> 1;
    if (L <= mid) rt1 = query(bt -> ls, lf, mid, L, R);
    if (R > mid)  rt2 = query(bt -> rs, mid + 1, rg, L, R);
    return max(rt1, rt2);
}
int main()
{
    init();
    n = read(), m = read();
    for (Acce i = 0; i <= n + 2; ++ i) root[i] = newnode(), root[i] -> rt = null; 
    for (Acce i = 1; i <= n; ++ i) 
    {
        a[i].v = read();
        a[i].pre = last[a[i].v] + 1;
        last[a[i].v] = i;
        a[i].id = i;
    }
    for (Acce i = 1; i <= n + 2; ++ i) last[i] = n + 1;
    for (Acce i = n; i; -- i)
    {
        a[i].nxt = last[a[i].v] + 1;
        last[a[i].v] = i;
    }
    sort(a + 1, a + n + 1);
    for (Acce i = 1; i <= n; ++ i)
    {
        while(las < a[i].pre - 1) root[las + 1] = root[las], ++ las;
        insert(root[a[i].pre], root[a[i].pre - 1], 1, n + 2, a[i]); 
        las = a[i].pre;
    }
    while(las <  n + 1) root[las + 1] = root[las], ++ las;
    for (Acce i = 1; i <= m; ++ i)
    {
        l = read(), r = read();
        l = (l + ans) % n + 1;
        r = (r + ans) % n + 1;
        if(l > r) swap(l, r);
        ++ l, ++ r;
        ans = query(root[l - 1], 1, n + 2, r + 1, n + 2);
        printf("%d\n", ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值