\(n\) 次操做, 維護一個一次函數集合 \(S\). 有兩種操做:php
\(n\le 1\times 10^5,x\le 50000\).c++
AFO前的打板子平常函數
講道理爲啥一個 \(\log\) 的題數據範圍纔給這麼點啊this
李超線段樹板子題.spa
李超線段樹是一種特殊的標記永久化線段樹. 說它特殊, 是由於標記雖然永久化可是依然會有下傳.code
在這種線段樹中, 每一個結點維護的是覆蓋當前結點的 "優點線段", 也即當前結點中有超過一半的 \(x\) 的查詢值在這個線段處取到. 因爲是一次函數, 實際上等價於當前結點的 \(mid\) 處以這個函數爲最大函數值.blog
當插入一個函數 \(f(x)\) 的時候, 分三種狀況:遞歸
查詢的時候就像普通的標記永久化線段樹同樣查到葉子並用路徑上的結點上存儲的優點線段更新答案.get
不難發現若是維護的是函數集合的話複雜度是一個 \(\log\) 的. 若是是在某一段特定區間上產生一個一次函數狀的貢獻的話, 會先將這個函數分佈到 \(O(\log n)\) 個結點上而後下傳, 這種狀況下時間複雜度是 \(O(\log^2 n)\) 的.it
李超樹的實際表現仍是很優秀的, 不少時候在區間維護的狀況下也不會比普通線段樹慢太多.
具體實現的時候善用 std::swap
能夠幫助減小冗餘代碼. 並且交點不用真的去求, 由於是一次函數因此判斷區間兩端的函數值的大小關係就知道它們是否在這個區間內相交了.
這道坑爹的板子題有幾個小坑點:
#include <bits/stdc++.h> const int MAXN=1e5+10; struct Line{ double k; double b; Line(double a,double b):k(a),b(b){} double operator()(const double& x)const{ return k*(x-1)+b; } }; struct Node{ int l; int r; Line f; Node* lch; Node* rch; Node(int,int); double Query(int); void Insert(Line); }; char buf[100]; int main(){ int q; bool flag=false; scanf("%d",&q); Node* N=new Node(1,5e4); while(q--){ scanf("%s",buf); if(*buf=='P'){ flag=true; double k,b; scanf("%lf%lf",&b,&k); N->Insert(Line(k,b)); } else{ int x; scanf("%d",&x); if(!flag) puts("0"); else printf("%d\n",int(N->Query(x)/100)); } } return 0; } double Node::Query(int x){ if(this->l==this->r) return this->f(x); else{ if(x<=this->lch->r) return std::max(this->f(x),this->lch->Query(x)); else return std::max(this->f(x),this->rch->Query(x)); } } void Node::Insert(Line f){ int mid=(this->l+this->r)>>1; if(f(mid)>this->f(mid)) std::swap(f,this->f); double ld=this->f(this->l)-f(this->l); double rd=this->f(this->r)-f(this->r); if(ld>=0&&rd>=0) return; else if(rd>=0) this->lch->Insert(f); else this->rch->Insert(f); } Node::Node(int l,int r):l(l),r(r),f(0,0){ if(l!=r){ int mid=(l+r)>>1; this->lch=new Node(l,mid); this->rch=new Node(mid+1,r); } }