「BZOJ3600」沒有人的算術 替罪羊樹+線段樹

来源:https://www.cnblogs.com/ModestStarlight/archive/2018/03/12/8552144.html
-Advertisement-
Play Games

題目描述 過長……不想發圖也不想發文字,所以就發鏈接吧…… [沒有人的算術][1] 題解 $orz$神題一枚 我們考慮如果插入的數不是數對,而是普通的數,這就是一道傻題了——直接線段樹一頓亂上就可以了。 於是我們現在只需要解決一個問題——維護這些數的大小關係。 由於這些數具有有序性,我們可以將這些數 ...


題目描述

過長……不想發圖也不想發文字,所以就發鏈接吧……
沒有人的算術

題解

\(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);
        }
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 序列 序列:數學上,序列是被排成一列的對象(或事件);這樣,每個元素不是在器他元素之前,就是在其他元素之後。這裡元素之間的順序非常重要。《維基百科》 序列是Python中最基本的數據結構。序列中的每個元素都分配一個數字 - 它的位置,或索引,第一個索引是0,第二個索引是1,依此類推。 Python有 ...
  • Description 已知va和vb分別為非遞減有序線性表,將va和vb進行合併為新的線性表vc,並保持vc仍然非遞減有序。 本題中,線性表元素為整數。線性表的最大長度為1000。 Input 輸入數據有多組,第一行為測試數據的組數t,接下來為2t行,每一組測試數據有兩行: 第一行的第一個數為va ...
  • 編寫第一個Java程式 完成工作:1.在文本編輯器中輸入一個Java程式。 2.使用括弧組織程式。 3.保存、編譯和運行程式。 1 package com.Jsample;//將程式的包名稱命名為com.Jsample 2 3 public class Helloworld {//將程式(類)命名為 ...
  • 列表函數 追加和擴展 list.append() 在列表末尾追加新的對象 extend()在列表末尾一次性追加另一個序列中的多個值(用新列表擴展原來的列表) 其他函數 count() 統計某個元素在列表中出現的次數 index() 從列表中找出某個值第一個匹配項的索引位置 insert() 將對象插 ...
  • 1 Celery簡介 Celery是非同步任務隊列,可以獨立於主進程運行,在主進程退出後,也不影響隊列中的任務執行。 任務執行異常退出,重新啟動後,會繼續執行隊列中的其他任務,同時可以緩存停止期間接收的工作任務,這個功能依賴於消息隊列(MQ、Redis)。 1.1 Celery原理 Celery的架構 ...
  • 1、方法的定義格式及解析 (1)方法概述:方法就是完成特定功能的代碼塊。 (2)定義格式: 修飾符 返回值類型 方法名(參數1,參數2,參數3...){ 函數體; return 返回值; } (3)修飾符:公共類public、私有類private、抽象類abstract、最終類final。 (4)返 ...
  • 測試環境:Keil 5.20.0.0 STM32F103RBT6 固件庫版本:STM32F10x_StdPeriph_Lib_V3.5.0(2011) 本文使用TIM1的通道1,通道2,產生兩路1khz,死區時間1us的互補PWM波。 所使用的IO口:由下圖知,我們使用引腳為PA9,PA10,互補輸 ...
  • 一.java常用數據類型 int 只有 true或false沒有0或非0 二.數據類型轉換 1.自動轉換:byte ->short int->char->int->long int ->float->double 轉換條件:由低類型向高類型(即箭頭所指的轉換方向)變數類型不會改變,但計算值會變為高類 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...