「BZOJ3600」没有人的算术 替罪羊树+线段树

题目描述

过长……不想发图也不想发文字,所以就发链接吧……
没有人的算术

题解

\(orz\)神题一枚
我们考虑如果插入的数不是数对,而是普通的数,这就是一道傻题了——直接线段树一顿乱上就可以了。

于是我们现在只需要解决一个问题——维护这些数的大小关系。

由于这些数具有有序性,我们可以将这些数的值重记为一个数,这样就可以无脑地比较了。并且,由于这些值的大小可能会随着插入而更改,所以要用一棵平衡树来维护。

那么问题来了,这个数取什么值比较好呢?
首先当然可以是排名,不过如果使用排名,每次访问值的时候都要重新在平衡树中查一次,复杂度肯定是\(O(nlog^2n)\)的,基本不现实。

换一个角度可以发现,我们只需要知道大小关系,不需要排名,于是我们可以用实数维护一个数的大小,虽然相邻的数差值大小不同,只要相对大小是正确的就不必担心了……

那么我们可以这样看,在平衡树中每个节点维护一个区间\((l,r)\),表示这棵子树中所有数值都在\((l,r)\)之中,而这棵子树的根的值为\(mid=\frac{(l+r)}{2}\)递归左右子树的时候,将区间分成\((l,mid)\)和\((mid,r)\),完全满足二叉搜索树的性质,并且可以随时在任何位置新增一个数而不影响已有的数的数值。

那么问题就得到了解决,我们用一棵平衡树维护出现过的所有数对,节点上保存这个数对的两个父亲和\((l,r)\),并用\(mid=\frac{(l+r)}{2}\)表示这个节点的数值,然后无脑做插入和询问即可。

但是我们忽略了一个问题,我们应该使用什么样的平衡树?发现那些基于旋转的平衡树在旋转后都会出现一个致命的问题,\((l,r)\)无法维护!因为一次旋转会使得它以及它的子树全部的\((l,r)\)都被改变,复杂度难以承受。于是我们想到了替罪羊树,基于重构的它反正都要全部拍扁了重来,重新为区间赋值不就可以在重构时顺便一起做了吗?

于是,这道题就这么被切掉了……
(内心:哪有说的这么简单)

总结:
替罪羊树的维护方式与\(AVL\)、\(SBT\)、\(Splay\)都不一样,后几种算法都是通过旋转维护平衡,然而替罪羊树却是用重构维护平衡,重构的时候可以重新计算值,不需要通过原来的值进行改变。所以替罪羊树可以维护的信息更加灵活,并且拍扁重建很欢乐常数小。于是替罪羊树非常适合套其他的数据结构……树套树时也要想一想能不能用替罪羊树……

\(Code:\)

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 100005
#define M 800005
#define eps 1e-10
#define inf 1e20
#define equal(a, b) (fabs((a) - (b)) < eps)
#define val(a) (a == -1 ? -inf :(ls[a] + rs[a])/ 2)
#define lim 0.77
char opt[10];
int n, m;
void Insert(int q, int l, int r, int k, int a);
int getint()
{
    int p=0;
    char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')p=p*10+c-'0',c=getchar();
    return p;
}
struct SCT
{
    int root, cnt, sz[M];
    int num, arr[M];
    int lc[M], rc[M];
    int lf[M], rf[M];
    double ls[M], rs[M];
    bool Equal(int u, int l, int r)
    {
        return equal(val(lf[u]), val(l)) && equal(val(rf[u]), val(r));
    }
    bool Less(int u, int l, int r)
    {
        return equal(val(lf[u]), val(l)) ? val(rf[u]) < val(r) : val(lf[u]) < val(l);
    }
    void Pushup(int q)
    {
        sz[q] = sz[lc[q]] + sz[rc[q]] + 1;
    }
    void Getarray(int q)
    {
        if (lc[q])
            Getarray(lc[q]);
        arr[++num] = q;
        if (rc[q])
            Getarray(rc[q]);
    }
    void Rebuild(int &q, int l, int r, double L, double R)
    {
        if (l > r)
        {
            q = 0;
            return;
        }
        int mid = (l + r) >> 1;
        double Mid = (L + R) / 2;
        q = arr[mid];
        ls[q] = L;
        rs[q] = R;
        Rebuild(lc[q], l, mid - 1, L, Mid);
        Rebuild(rc[q], mid + 1, r, Mid, R);
        Pushup(q);
    }
    void Maintain(int &q)
    {
        num = 0;
        Getarray(q);
        Rebuild(q, 1, num, ls[q], rs[q]);
    }
    void Add(int &q, int l, int r, double L, double R, int k)
    {
        if (!q)
        {
            q = ++cnt;
            lf[q] = l, rf[q] = r;
            ls[q] = L, rs[q] = R;
            sz[q] = 1;
            Insert(1, 1, n, k, cnt);
            return;
        }
        if (Equal(q, l, r))
        {
            Insert(1, 1, n, k, q);
            return;
        }
        if (Less(q, l, r))
            Add(rc[q], l, r, (L + R) / 2, R, k);
        else
            Add(lc[q], l, r, L, (L + R) / 2, k);
        Pushup(q);
    }
    void Check(int &q, int l, int r)
    {
        if (sz[q] * lim < sz[lc[q]] || sz[q] * lim < sz[rc[q]])
        {
            Maintain(q);
            return;
        }
        if (Equal(q, l, r))
            return;
        if (Less(q, l, r))
            Check(rc[q], l, r);
        else
            Check(lc[q], l, r);
    }
}T;
struct data
{
    int plc, num;
    data(){}
    data(int a, int b){plc = a, num = b;}
    double Val(){return (T.ls[num] + T.rs[num])/ 2;}
    data operator + (data b)
    {
        if (num == 1 && b.num == 1)
            return plc < b.plc ? *this : b;
        if (num == 1)
            return b;
        if (b.num == 1)
            return *this;
        if (equal(Val(), b.Val()))
            return plc < b.plc ? *this : b;
        if (Val() > b.Val())
            return *this;
        return b;
    }
};
struct SGT
{
    data a[N << 2];
}S;
void Insert(int q, int l, int r, int k, int a)
{
    if (l == k && r == k)
    {
        S.a[q] = data(k, a);
        return;
    }
    int mid = (l + r) >> 1;
    if (k <= mid)
        Insert(q << 1, l, mid, k, a);
    else
        Insert(q << 1 | 1, mid + 1, r, k, a);
    S.a[q] = S.a[q << 1] + S.a[q << 1 | 1];
}
data Query(int q, int l, int r, int L, int R)
{
    if (l == L && r == R)
        return S.a[q];
    int mid = (l + r) >> 1;
    if (R <= mid)
        return Query(q << 1, l, mid, L, R);
    if (L > mid)
        return Query(q << 1 | 1, mid + 1, r, L, R);
    return Query(q << 1, l, mid, L, mid) + Query(q << 1 | 1, mid + 1, r, mid + 1, R);
}
int main()
{
    n = getint(), m = getint();
    T.root = T.cnt = 1;
    T.lf[1] = T.rf[1] = -1;
    T.ls[1] = 0, T.rs[1] = 1;
    for (int i = 1; i <= n; i++)
        Insert(1, 1, n, i, 1);
    for (int i = 1; i <= m; i++)
    {
        scanf("%s", opt);
        if (opt[0] == 'Q')
        {
            int l = getint(), r = getint();
            printf("%d\n", Query(1, 1, n, l, r).plc);
        }
        else
        {
            int l = getint(), r = getint(), k = getint();
            l = Query(1, 1, n, l, l).num;
            r = Query(1, 1, n, r, r).num;
            T.Add(T.root, l, r, 0, 1, k);
            T.Check(T.root, l, r);
        }
    }
}

原文地址:https://www.cnblogs.com/ModestStarlight/p/8552144.html

时间: 2024-11-08 23:54:43

「BZOJ3600」没有人的算术 替罪羊树+线段树的相关文章

「luogu3380」【模板】二逼平衡树(树套树)

「luogu3380」[模板]二逼平衡树(树套树) 传送门 我写的树套树--线段树套平衡树. 线段树上的每一个节点都是一棵 \(\text{FHQ Treap}\) ,然后我们就可以根据平衡树的基本操作以及线段树上区间信息可合并的性质来实现了,具体细节看代码都懂. 参考代码: #include <algorithm> #include <cstdlib> #include <cstdio> #define rg register #define file(x) freo

[题解] bzoj 3600 没有人的算数 (替罪羊树+线段树)

- 传送门 - http://www.lydsy.com/JudgeOnline/problem.php?id=3600 - 思路 - 区间操作是线段树无疑,难点在于如何考虑这些数对的大小关系. 我们考虑一下平衡树,如果在平衡树中每个节点放一个数对,我们规定中序遍历前面的数对大于后面的,那么对于任意一个新数对(x,y)插入时只需知道x,y与每个节点的数对的关系,就可以在log的时间内放入. 对于x,y与某节点数对的关系,首先要知道x,y一定在平衡树中存在(否则怎么被用来构成新数对?),因此可以l

【bzoj3600】没有人的算术

替罪羊树模版题. 我们发现原式无法比较大小. 用平衡树为每个式子分配一个值,用来比较大小.树上每个节点有l,r,这个值为l+r.左节点为l,mid,右节点同理.这样分配值显然可以比较大小了. 但该平衡树不能旋转,于是用替罪羊树即可. siz要在好几处修改..调了好久.. #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cmat

「杂谈」苏州人不能太膨胀

上半年苏州房价上涨比较快,园区核心区房价远超上海滩很多区域,以至于很多人说上海变成了环苏城市,或者说卖掉上海的房子置换到苏州也比较难. 笔者不以为然,我觉得这只是一些苏州人尤其是在园区有房子的人过于乐观以至于太膨胀. 苏州无论哪一方面跟上海比,都完全无优势.无论是经济实力,经济质量,人口体量,政治地位,教育医疗资源,上海都轻松碾压苏州. 说到房价,不能拿苏州最贵的房子跟上海的郊区房价作比较,这是苏州不自信的表现.要比较房价,应该拿苏州核心区与上海核心区比较,苏州郊区与上海郊区比较.这才是靠谱与合

【bzoj2141】排队 [国家集训队2011]排队(魏铭) 树套树 线段树套替罪羊树

这个题就是动态偏序对,每次操作做两个删除两个插入就好了. #include<cstdio> #include<iostream> #include<cstring> #define MAXN 100010 using namespace std; typedef long long LL; typedef double D; const D a=0.756; LL ans; struct ScapeGoat_Tree { ScapeGoat_Tree *ch[2]; i

AC日记——「SCOI2015」情报传递 LiBreOJ 2011

#2011. 「SCOI2015」情报传递 思路: 可持久化树状数组模板: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 200005 #define maxm maxn*100 int deep[maxn],f[maxn],id[maxn],top[maxn],cnt,soot; int head[maxn],V[maxn],E[maxn],lar[maxn],size[maxn]; int val[maxm],

「SPOJ1487」Query on a tree III

「SPOJ1487」Query on a tree III 传送门 把树的 \(\text{dfs}\) 序抠出来,子树的节点的编号位于一段连续区间,然后直接上建主席树区间第 \(k\) 大即可. 参考代码: #include <algorithm> #include <cstdio> #define rg register #define file(x) freopen(x".in", "r", stdin), freopen(x"

「ZJOI2011」最小割

「ZJOI2011」最小割 传送门 建出最小割树,然后暴力计算任意两点之间最小割即可. 多组数据记得初始化. 参考代码: #include <algorithm> #include <cstring> #include <cstdio> #include <queue> #define rg register #define file(x) freopen(x".in", "r", stdin), freopen(x&

「luogu2633」Count on a tree

「luogu2633」Count on a tree 传送门 树上主席树板子. 每个节点的根从其父节点更新得到,查询的时候差分一下就好了. 参考代码: #include <algorithm> #include <cstdio> #define rg register #define file(x) freopen(x".in", "r", stdin), freopen(x".out", "w", s