1. 程式人生 > 其它 >21.6.1 t3

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;
}