21.6.1 t3
tag:平衡樹,二分
最優策略中一個點一定只選一次,否則只需要選最後一次就行了。
在確定取的點集後,按 \(t\) 從小到大取最優。
考慮 \(n^2\) 暴力,\(s\) 最大的那個一定出現在所有方案中,否則用它替換第一個點一定更優。(這裡只是說明最大的一定出現,不代表它一定是第一個)
設最大的 \(s\) 為 \(s_{mx}\),對於
-
\(t_i<t_{mx}\),\(s_i\) += \(t_{mx}\),表示選了 \(i\) 以後,\(mx\) 這一堆會往後拖一天
-
\(t_i\ge t_{mx}\),\(s_i\) += \(t_i\),表示 \(i\) 會往後拖一天
這樣處理完以後就遞迴到了 \(n-1\) 的問題。
根據遞迴解法,發現選 \(k\) 個點的最優集合一定是某個選 \(k+1\) 個點的最優集合的子集。
所以最優方案選的點可以用一個排列 \(p_1\cdots p_n\) 表示,其中 \(p_1\cdots p_k\) 表示選 \(k\) 個點的最優集合。
考慮如何求出這個排列。
按照 \(t\) 從小到大插入,由於當前插入的 \(t\) 是當前最大的,所以這個點在上文的暴力(只考慮當前插入的點)中,遞迴到第 \(i\) 層問題時的 \(s'=t\cdot i+s\),所以問題變為這個 \(s'\) 在遞迴到哪一層問題時會成為最大值,並被選入集合。
這個問題是具有二分性的,因為當前插入的 \(t\) 是最大的,所以它的 \(s'\) 的增長速度是最快的,在第一次成為最大值之後的每一層遞迴中都是最大值。
而且因為當前 \(t\) 是最大的,所以對於當前答案排列中它後面的所有 \(s_i\) 都要加上 \(t\)(根據上文暴力)。
因為涉及到動態插入,二分和區間加,可以用平衡樹解決。
最終平衡樹的中序遍歷的前 \(k\) 項求和就是選 \(k\) 個的答案。
複雜度 \(O(nlogn)\)
程式碼實現上,插入可以直接跟在二分後面,不用找rk再找位置。
#include<bits/stdc++.h> using namespace std; template<typename T> inline void Read(T &n){ char ch; bool flag=false; while(!isdigit(ch=getchar()))if(ch=='-')flag=true; for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48)); if(flag)n=-n; } enum{ MAXN = 500005 }; typedef long long ll; int n; struct node{ int son[2], fa, sz; ll val, add; #define lc(x) t[x].son[0] #define rc(x) t[x].son[1] #define fa(x) t[x].fa #define sz(x) t[x].sz #define val(x) t[x].val #define add(x) t[x].add }t[MAXN]; int node_cnt, root; inline char Which(int x){return rc(fa(x))==x;} inline void Push_Up(int x){sz(x) = sz(lc(x))+sz(rc(x))+1;} inline void Add(int x, ll add){ add(x) += add; val(x) += add; } inline void Push_Down(int x){ if(add(x)){ if(lc(x)) Add(lc(x),add(x)); if(rc(x)) Add(rc(x),add(x)); add(x) = 0; } } inline void Rotate(int x){ int y=fa(x), z=fa(y), k=Which(x), u=t[x].son[!k]; if(z) t[z].son[Which(y)]=x; else root=x; fa(x)=z; if(u) fa(u)=y; t[y].son[k]=u; fa(y)=x; t[x].son[!k]=y; Push_Up(y); Push_Up(x); } inline void Splay(int x, int Tar=0){ while(fa(x)!=Tar){ int y=fa(x), z=fa(y); if(z!=Tar) Rotate(Which(x)==Which(y)?y:x); Rotate(x); } } struct ele{ ll s, t; inline ll f(int x){return s+t*(x-1);} inline bool operator <(const ele &k)const{return t<k.t;} }a[MAXN]; void Insert(int &x, int y, ele k, int rk){ if(!x){ x = ++node_cnt; val(x) = k.f(rk+1); fa(x) = y; sz(x) = 1; int tx=x; Splay(tx); if(rc(tx)) Add(rc(tx),k.t); return; } Push_Down(x); if(k.f(rk+sz(lc(x))+1) < val(x)) Insert(rc(x),x,k,rk+sz(lc(x))+1); else Insert(lc(x),x,k,rk); } ll s; void Check(int x){ if(!x) return; Push_Down(x); Check(lc(x)); s += val(x); cout<<s<<'\n'; Check(rc(x)); } int main(){ // freopen("3.in","r",stdin); // freopen("3.out","w",stdout); Read(n); for(register int i=1; i<=n; i++) Read(a[i].s), Read(a[i].t); sort(a+1,a+n+1); for(register int i=1; i<=n; i++) Insert(root,0,a[i],0);// Check(root), puts(""); Check(root); return 0; }