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