日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

【李超树】李超线段树维护凸包(凸壳) (例题:blue mary开公司+线段游戏+ZZH的旅行)

發布時間:2023/12/3 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【李超树】李超线段树维护凸包(凸壳) (例题:blue mary开公司+线段游戏+ZZH的旅行) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • 李超樹
    • 引入(斜率優化)
    • 什么是李超樹?
    • 李超樹活著能干點什么?
  • 算法思想(使用手冊?)
    • 插入
    • 查詢
  • 模板
    • 判斷是否覆蓋(優不優)
    • 插入
    • 查詢
  • 例題
    • 板題:BlueMary開公司
      • 分析
      • code
    • 線段游戲
      • 分析
      • code
  • 拓展——(動態開點李超樹維護凸包)
    • ZZH的旅行
      • solution
      • code

前言

最近兩場xie教練的考試都拉到了動態維護多條直線求最值的凸包問題
然后很愉快的一道都做不出來呢
所以學習了一下下,就寫了個小博客

李超樹

引入(斜率優化)

學習了c++c++c++,就必少不了dpdpdp的花式玩法
也就不會錯過各種O(1),O(log)O(1),O(log)O(1),O(log)的優化
前綴和…斜率優化…單調隊列…單調?!?/p>


我們看一個dpdpdp狀態轉移方程式
dp[i]=max{dp[j]+bi?aj+a[i]},j<idp[i]=max\{dp[j]+b_i*a_j+a[i]\},j<idp[i]=max{dp[j]+bi??aj?+a[i]},j<i

一般這種長相,噢不,準確來說,很多dpdpdp的這種長相,都會跟凸包掛鉤

因為出題人不想考那么板的dp送分,就只能搞搞單調讓你優化
而我們目前已知的解決凸包就是斜率優化


假設題目保證aia_iai?遞增

假設j<k<ij<k<ij<k<i且決策點jjj優于決策點kkk,則有
dp[j]+bi?aj+a[i]>dp[k]+bi?ak+b[i]①dp[j]+b_i*a_j+a[i]>dp[k]+b_i*a_k+b[i]\ \ \ \ \ \ \ ①dp[j]+bi??aj?+a[i]>dp[k]+bi??ak?+b[i]???????dp[j]?dp[k]>bi?(ak?aj)②dp[j]-dp[k]>b_i*(a_k-a_j)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ②dp[j]?dp[k]>bi??(ak??aj?)????????????????????????????????題目保證aaa單增,即ak?aj>0a_k-a_j>0ak??aj?>0,繼續變形得到
dp[j]?dp[k]ak?aj>bi③\frac{dp[j]-dp[k]}{a_k-a_j}>b_i\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ③ak??aj?dp[j]?dp[k]?>bi????????????????????????????????????????????????????
上述過程其實就是斜率優化的推導過程,這種情況下我們仍然可以吃老本——走斜率優化


但是,如果題目不保證aia_iai?單調呢??這個時候,斜率優化可以慢走不送了

這個條件去掉后,為什么斜率優化不行了呢??
再回到上面的推導過程,②能推導出③,實際上是把ak?aja_k-a_jak??aj?除了過去
也就是說我們是肯定了ak?aja_k-a_jak??aj?的符號才敢這樣轉移
一旦不確定了,大于小于符號就會錯亂,無法斜率優化
ps:我剛剛偷換了一下,我把單增變成了題目不保證單調,因為單減也是可以斜率優化的

所以斜率優化是建立在單調的基礎上的


此時我們怎么辦呢??

  • 暴力的氣息撲面而來
  • 祈禱數據會有單調的(概率:0.00…01%) 除非你是出數據的
  • 亂搞
  • 其它優秀的做法
  • 我們的主角當當當————李超樹!!!!yyds!
  • 什么是李超樹?

    李超樹 額(⊙﹏⊙)…按xie教練的話就是——懶標記永久化的線段樹

    明明就是個懶標記永久化,搞不懂為什么要專門取個名字叫李超樹 ·····················——xie教練

    管他的,反正我們不需要了解,它能有點用才是我們關注的!

    李超樹活著能干點什么?

  • 維護一段區間的多條直線
  • 支持單點查詢多條直線的極值,如查詢xxx處的多條直線的縱坐標最大值
  • 支持區間查詢直線極值,如查詢區間[l,r][l,r][l,r]中各直線最值的最大值
  • 本篇博客僅從“單點查詢多條直線的極值”入手,因為博主目前只會這一種

    只要你能寫出 f[i]=max/min(f[j]?h(i)+g[j])+t(i)f[i]=max/min(f[j]*h(i)+g[j])+t(i)f[i]=max/min(f[j]?h(i)+g[j])+t(i)
    搞那么復雜干什么, 其實就是你能寫出一條一次函數的解析式 y=kx+by=kx+by=kx+b
    李超樹就能硬剛!

    算法思想(使用手冊?)

    維護上凸包和下凸包差不多,我們以維護最大值為例

    李超樹每個區間記錄的是該區間中點midmidmid處的最大/小函數值對應的函數

    插入

    分兩種情況

  • 完全覆蓋

    新的紅線在[l,r][l,r][l,r]區間上完全優于原來該區間存的直線,那么將直接進行全覆蓋
    然后就returnreturnreturn,不用去更新左右子樹
    至于為什么?跟查詢寫法有關,也跟部分覆蓋掛鉤
    因為此時更新后如果沒有出現部分覆蓋的情況,查詢我們也會查到這條直線的值
    如果出現了部分覆蓋的情況,此時的紅線就會部分下放更新
    反正此時的黃線已經是個廢物了,只需要知道這點就o了
  • 部分覆蓋
    2-1.

    首先這個區間[l,r][l,r][l,r]存的直線與midmidmid掛鉤,此時紅線在midmidmid處的函數值優于黃線
    所以李超線段樹[l,r][l,r][l,r]這一個區間就會存紅線的解析式
    那這個黃線呢??它也并不是全無用,我們需要繼續遞歸右子樹,去更新藍色部分的區間

    2-2.

    此時紅線在midmidmid處的函數值劣于黃線,李超線段樹[l,r][l,r][l,r]這一個區間的解析式仍然是黃線
    但這個紅線呢??也并不是全無用,我們需要繼續遞歸做子樹,去更新藍色部分的區間
  • 這三種情況怎么判斷呢?就是怎么寫呢?
    很簡單——直線是單調的
    我們只需要掌握l,mid,rl,mid,rl,mid,r三個點的兩點函數值就可以了,具體可看模板

    查詢

    每一個區間都存了一條直線,可能相同也可能不同
    一個點被多個區間包含,也就會被多條直線包含
    所以我們一路上下來遇到的每一個區間都要算一次xxx對應的函數值,取最大值
    有可能先遇到的直線的函數值比后遇到的直線的函數值還小,這是有可能的
    因為那些區間的函數解析式是根據那些區間的中點midimid_imidi?的函數值決定的
    誰管你這個小蝦皮
    舉個栗子

    這一路上涉及到的四個點的每一條解析式(黑線)都要把xxx帶進去算出y1,y2,y3,y4y_1,y_2,y_3,y_4y1?,y2?,y3?,y4?

    模板

    判斷是否覆蓋(優不優)

    double calc( node num, int x ) {return num.k * x + num.b; }bool cover( node old, node New, int x ) {return calc( old, x ) <= calc( New, x ); }

    插入

    void insert( int num, int l, int r, node New ) {if( cover( t[num], New, l ) && cover( t[num], New, r ) ) {//新的直線完全覆蓋了原區間的最優直線 t[num] = New;return;}if( l == r ) return;int mid = ( l + r ) >> 1;if( cover( t[num], New, mid ) )//區間[l,r]維護x=mid的最優值 swap( t[num], New );//現在這條直線需要繼續往下去更新左右兒子(如果這條直線更優) if( cover( t[num], New, l ) ) insert( num << 1, l, mid, New );if( cover( t[num], New, r ) )insert( num << 1 | 1, mid + 1, r, New ); }

    查詢

    這個版本有限制!!!
    這個版本有限制!!!
    這個版本有限制!!!

    也許是我寫法問題
    此版本查詢維護的是直線,也就是說每一條直線都會覆蓋到所有的查詢點
    我們知道[l,r][l,r][l,r]區間維護的函數解析式是當x=midx=midx=mid時,函數值最大的那條解析式
    因為每一條線都能覆蓋完,所以每一個區間[l,r][l,r][l,r]都會維護一條直線
    于是乎此版本的優化:x=midx=midx=mid時直接返回,不再尋找左右兒子
    在這個前提下,這個優化才有正確性保障

    double query( int num, int l, int r, int x ) {double ans = -1e9;int mid = ( l + r ) >> 1;if( x < mid ) ans = query( num << 1, l, mid, x );if( mid < x ) ans = query( num << 1 | 1, mid + 1, r, x );return max( ans, calc( t[num], x ) ); }

    這一個版本維護的是線段,可能只會覆蓋到一部分查詢點

    對應的插入操作也會發生改變,[l,r][l,r][l,r]能插入當且僅當[l,r][l,r][l,r]被某條直線完全包含,我們要再寫一個modifymodifymodify

    void modify( int num, int l, int r, int L, int R, node New ) {if( L > R ) return;if( L <= l && r <= R ) {insert( num, l, r, New );return;}int mid = ( l + r ) >> 1;if( R <= mid ) modify( num << 1, l, mid, L, R, New );else if( mid < L ) modify( num << 1 | 1, mid + 1, r, L, R, New );else {modify( num << 1, l, mid, L, mid, New );modify( num << 1 | 1, mid + 1, r, mid + 1, R, New );} }

    也就是說[l,r][l,r][l,r]區間如果沒有被一條直線完全覆蓋的話,這個區間是沒有存直線的
    那么當我們繼續沿用之前的優化,x=midx=midx=mid就返回,很可能這段區間壓根沒有直線解析式
    所以我們必須繼續詢問左兒子或者右兒子

    打破砂鍋問到底

    double query( int num, int l, int r, int x ) {double ans;if( l == r ) return calc( t[num], x - 1 );int mid = ( l + r ) >> 1;if( x <= mid ) ans = query( num << 1, l, mid, x );else ans = query( num << 1 | 1, mid + 1, r, x );return max( ans, calc( t[num], x - 1 ) ); }

    直線也可看作一條無限長的線段,因此此版本適用范圍廣

    如果不是很理解,下面兩道例題就能很好的辨析出來,┏ (゜ω゜)=?
    線段游戲的樣例就會發現查詢的寫法不同會有問題,可以分布調試看看

    例題

    板題:BlueMary開公司

    分析

    每一個項目看作一條直線,而某一天就是查詢點
    依題意可得,每一個項目可以覆蓋每一天,也就是直線覆蓋所有點情況
    兩種查詢模板都可以直接上

    code

    #include <cstdio> #include <iostream> using namespace std; #define maxn 50005 struct node {double k, b;node(){}node( double K, double B ) {k = K, b = B;} }t[maxn << 2]; int n;double calc( node num, int x ) {return num.k * x + num.b; }bool cover( node old, node New, int x ) {return calc( old, x - 1 ) <= calc( New, x - 1 ); //題目原因 符合要求的x實際上是需要-1才算得出正確收益 }void insert( int num, int l, int r, node New ) {if( cover( t[num], New, l ) && cover( t[num], New, r ) ) {t[num] = New;return;}if( l == r ) return;int mid = ( l + r ) >> 1;if( cover( t[num], New, mid ) )swap( t[num], New );if( cover( t[num], New, l ) ) insert( num << 1, l, mid, New );if( cover( t[num], New, r ) )insert( num << 1 | 1, mid + 1, r, New ); }double query( int num, int l, int r, int x ) {double ans = -1e9;int mid = ( l + r ) >> 1;if( x < mid ) ans = query( num << 1, l, mid, x );if( mid < x ) ans = query( num << 1 | 1, mid + 1, r, x );return max( ans, calc( t[num], x - 1 ) ); } /* 已測試,此寫法依然可過 double query( int num, int l, int r, int x ) {double ans;if( l == r ) return calc( t[num], x - 1 );int mid = ( l + r ) >> 1;if( x <= mid ) ans = query( num << 1, l, mid, x );else ans = query( num << 1 | 1, mid + 1, r, x );return max( ans, calc( t[num], x - 1 ) ); } */ int main() {scanf( "%d", &n );while( n -- ) {char opt[10]; int k, b, t;scanf( "%s", opt );if( opt[0] == 'Q' ) {scanf( "%d", &t );printf( "%d\n", int( query( 1, 1, maxn, t ) / 100 ) );}else {double k, b;scanf( "%lf %lf", &b, &k );insert( 1, 1, maxn, node( k, b ) );}} return 0; }

    線段游戲

    分析

    這道題就很特別了,我們維護的不再是一條直線,而是一條線段
    這條線段只能覆蓋有限的查詢點,而不能覆蓋的點我們是不能進行求解的

    code

    #include <cstdio> #include <iostream> using namespace std; #define maxn 100000 #define inf 0x7f7f7f7f struct node {double k, b;node(){}node( double K, double B ) {k = K, b = B;} }t[( maxn + 5 ) << 2]; int n, m;double calc( node num, int x ) {return num.k * x + num.b; }bool cover( node old, node New, int x ) {return calc( old, x ) < calc( New, x ); }void insert( int num, int l, int r, node New ) {if( cover( t[num], New, l ) && cover( t[num], New, r ) ) {t[num] = New;return;}if( l == r ) return;int mid = ( l + r ) >> 1;if( cover( t[num], New, mid ) )swap( t[num], New );if( cover( t[num], New, l ) )insert( num << 1, l, mid, New );if( cover( t[num], New, r ) )insert( num << 1 | 1, mid + 1, r, New ); }void modify( int num, int l, int r, int L, int R, node New ) {if( L > R ) return;if( l == L && R == r ) {insert( num, l, r, New );return;}int mid = ( l + r ) >> 1;if( R <= mid ) modify( num << 1, l, mid, L, R, New );else if( mid < L ) modify( num << 1 | 1, mid + 1, r, L, R, New );else {modify( num << 1, l, mid, L, mid, New );modify( num << 1 | 1, mid + 1, r, mid + 1, R, New );} }double query( int num, int l, int r, int x ) {if( l == r ) return calc( t[num], x );double ans;int mid = ( l + r ) >> 1;if( x <= mid ) ans = query( num << 1, l, mid, x );else ans = query( num << 1 | 1, mid + 1, r, x );return max( ans, calc( t[num], x ) ); /* 這種寫法是不對的,原因上面已經分析過了......double ans = -inf;int mid = ( l + r ) >> 1;if( x < mid ) ans = query( num << 1, l, mid, x );if( mid < x ) ans = query( num << 1 | 1, mid + 1, r, x );return max( ans, calc( t[num], x ) ); */ }void build( int num, int l, int r ) {t[num].k = 0, t[num].b = -inf;if( l == r ) return;int mid = ( l + r ) >> 1;build( num << 1, l, mid ), build( num << 1 | 1, mid + 1, r ); }int main() {scanf( "%d %d", &n, &m );build( 1, 1, maxn );int x0, x1, y1, x2, y2, opt;double k, b;for( int i = 1, x1, y1, x2, y2;i <= n;i ++ ) {scanf( "%d %d %d %d", &x1, &y1, &x2, &y2 );if( x1 == x2 ) k = 0, b = max( y2, y1 );else k = ( y2 - y1 ) * 1.0 / ( x2 - x1 ), b = y1 - k * x1;modify( 1, 1, maxn, max( 1, min( x1, x2 ) ), min( maxn, max( x1, x2 ) ), node( k, b ) );}while( m -- ) {scanf( "%d", &opt );if( opt ) {scanf( "%d", &x0 );double ans = query( 1, 1, maxn, x0 );printf( "%.6f\n", ( ans <= -inf ? 0 : ans ) );}else {scanf( "%d %d %d %d", &x1, &y1, &x2, &y2 );if( x1 == x2 ) k = 0, b = max( y2, y1 );else k = ( y2 - y1 ) * 1.0 / ( x2 - x1 ), b = y1 - k * x1;modify( 1, 1, maxn, max( 1, min( x1, x2 ) ), min( maxn, max( x1, x2 ) ), node( k, b ) );}}return 0; }

    拓展——(動態開點李超樹維護凸包)

    ZZH的旅行



    solution

    code

    有一坨不像我🐎風的快讀快輸代碼,是因為我TTT了,真的?不動了,加了就能AAA

    #include <cstdio> #include <vector> #include <algorithm> using namespace std; #define int long long #define maxn 1000005 vector < pair < int, int > > G[maxn]; int n; int aa[maxn], bb[maxn], dep[maxn]; int X[maxn], K[maxn], B[maxn], rt[maxn], dp[maxn]; int son[maxn][2];namespace IO{const int sz=1<<22;char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];inline char gc(){return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;}template<class T> void read(T& x){x=0; char c=gc();for(;c<'0'||c>'9';c=gc());for(;c>='0'&&c<='9';c=gc())x=x*10+(c-'0');}inline void flush(){fwrite(b,1,t-b,stdout),t=b; }inline void pc(char x){*t++=x; if(t-b==sz) flush(); }template<class T> void print(T x,char c='\n'){if(x==0) pc('0'); int t=0;for(;x;x/=10) p[++t]=x%10+'0';for(;t;--t) pc(p[t]); pc(c);}struct F{~F(){flush();}}f; } using IO::read; using IO::print;int insert( int x, int l, int r, int k, int b, int id ) {if( ! x ) {son[id][0] = son[id][1] = 0;K[id] = k, B[id] = b;return id;}int mid = ( l + r ) >> 1;if( X[mid] * K[x] + B[x] < X[mid] * k + b )swap( K[x], k ), swap( B[x], b );if( l < r ) {if( k >= K[x] ) son[x][1] = insert( son[x][1], mid + 1, r, k, b, id );else son[x][0] = insert( son[x][0], l, mid, k, b, id );}return x; }int query( int x, int l, int r, int id ) {if( ! x ) return -1e18;int ans = K[x] * X[id] + B[x];int mid = ( l + r ) >> 1;if( id <= mid ) ans = max( ans, query( son[x][0], l, mid, id ) );else ans = max( ans, query( son[x][1], mid + 1, r, id ) );return ans; }int merge( int x, int y, int l, int r ) {if( ! x || ! y ) return x + y;int mid = ( l + r ) >> 1;son[x][0] = merge( son[x][0], son[y][0], l, mid );son[x][1] = merge( son[x][1], son[y][1], mid + 1, r );if( K[x] * X[mid] + B[x] < K[y] * X[mid] + B[y] ) swap( K[x], K[y] ), swap( B[x], B[y] );if( l < r ) insert( x, l, r, K[y], B[y], y );return x; }void dfs1( int u, int fa ) {for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first, w = G[u][i].second;if( v == fa ) continue;else dep[v] = dep[u] + w, dfs1( v, u );} }void dfs2( int u, int fa ) {for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i].first;if( v == fa ) continue;else dfs2( v, u ), rt[u] = merge( rt[u], rt[v], 1, n );}dp[u] = max( 0ll, query( rt[u], 1, n, lower_bound( X + 1, X + n + 1, aa[u] + dep[u] ) - X ) );rt[u] = insert( rt[u], 1, n, bb[u], dp[u] - bb[u] * dep[u], u ); }signed main() {read( n );for( int i = 1;i <= n;i ++ )read( aa[i] ), read( bb[i] );for( int i = 1, u, v, d;i < n;i ++ ) {read( u ), read( v ), read( d );G[u].push_back( make_pair( v, d ) );G[v].push_back( make_pair( u, d ) );}dfs1( 1, 0 );for( int i = 1;i <= n;i ++ )X[i] = aa[i] + dep[i];sort( X + 1, X + n + 1 );dfs2( 1, 0 );for( int i = 1;i <= n;i ++ )print( dp[i] );return 0; } 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的【李超树】李超线段树维护凸包(凸壳) (例题:blue mary开公司+线段游戏+ZZH的旅行)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。