line
有\(n\)个人按编号从\(1\)到\(n\)排成一列,每个人有三个属性值\(l_i\)、\(t_i\)和\(w_i\),现在要将这些人分成连续的若干段,记第\(i\)段(编号\(l\)到编号\(r\))中最大的\(t\)值为\(maxt\),那么这段的代价就是\(maxt\sum\limits_{j=r+1}^nw_j\),并且有限制,如果一段中最大的编号为\(x\),那么编号为\(l_x\)的人不能在这个段内,求划分段的最小代价
数据范围\(1\leq n\leq 10^5\)
Solution
首先可以列一个朴素的dp方程:
记\(f[i]\)表示前\(i\)个人的划分最小代价,那么有\(f[i]=min(f[j-1]+suf[i+1]*mx(j,i))\),其中\(j\leq i\),\(suf\)表示\(w\)的后缀和,\(mx(l,r)\)表示\(l\)到\(r\)这段中\(t\)的最大值
然后考虑怎么优化这个东西
get到一个操作:处理这种带\(mx\)的转移可以借助单调栈
我们用单调栈维护\(t\)的最大值,假设当前处理到第\(i\)个人,前\(i-1\)个\(f\)值都已经求出,现在我们要求出\(f[i]\),此时根据单调栈中存的元素我们可以将\(i\)前面的位置划分成若干段,每段里面\(t\)的最大值\(maxt\)相同
而根据上面的\(dp\)式子,对于\(maxt\)相同的一段,\(suf[i+1]\)是定值,那么\(min(f[j-1]+suf[i+1]*maxt)\)显然在\(f[j-1]\)取\(min\)的时候最小,由于前面的\(f\)已经全部求出,也就是说对于\(maxt\)相同的一段,这个\(min\)值是确定的
所以其实我们要做的是比较这若干段\(maxt\)谁更优,然后发现这个东西可以斜率优化,推一推得到一个\(\frac{min_f[j]-min_f[k]}{maxt_j-maxt_k}>\)(与\(suf[i+1]\)有关的常数,没记错应该是带个负号不想再推一遍了qwq)这样的形式,也就是说我们将\((maxt,min_f)\)看成点,维护上凸壳就好了,每次查询的时候因为另一边也是单调的所以不需要二分也可以直接单调栈扫
最后一个问题就是怎么维护凸壳:我们现在要支持一个删除点、新加点、以及区间查询(下标就是单调栈中的下标,之所以是区间查询是因为有\(l\)的限制)的操作,这里有一个比较好的处理方式(其实就是二进制分组)
实现的方式的话我用的是线段树:首先将\(n\)补成\(2\)的整数次幂,然后对于一个长度为\(2\)的正整数幂的区间(其实就是线段树上每个节点了),只有在这个区间内每一个位置都有点的时候才计算这个区间的凸壳,一旦一个区间变得不完整就将这个区间原来维护的凸壳删掉,查询的时候一直走直到碰到一个范围内的计算出来的凸壳才返回它的答案(而不是像普通线段树一样一遇到范围内的区间就返回)
然后就做完了ovo
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#define ll long long
#define pb push_back
using namespace std;
const int N=1e5+10,TOP=20;
const ll inf=1LL<<60;
struct Dot{
ll x,y;
Dot(ll _x=0,ll _y=0){x=_x; y=_y;}
friend ll cross(Dot a,Dot b){return a.x*b.y-a.y*b.x;}
friend Dot operator - (Dot a,Dot b){return Dot(a.x-b.x,a.y-b.y);}
friend bool operator < (Dot a,Dot b){return a.x==b.x?a.y<b.y:a.x<b.x;}
void print(){printf("(%lld,%lld)",x,y);}
};
void print(vector<Dot> x){
int sz=x.size();
for (int i=0;i<sz;++i) x[i].print();
printf("\n");
}
namespace Seg{/*{{{*/
const int N=::N*4;
int ch[N][2],full[30],cnt[N],p[N];
vector<Dot> h[N],tmp;
Dot st[N];
int n,tot;
void _build(int x,int l,int r){
if (l==r) return;
int mid=l+r>>1;
ch[x][0]=++tot; _build(ch[x][0],l,mid);
ch[x][1]=++tot; _build(ch[x][1],mid+1,r);
}
void build(int _n){
n=1;
while(n<_n) n<<=1;
tot=1;
_build(1,1,n);
}
void get_hull(int x){
int sz;
tmp.clear();
sz=h[ch[x][0]].size();
for (int i=0;i<sz;++i) tmp.pb(h[ch[x][0]][i]);
sz=h[ch[x][1]].size();
for (int i=0;i<sz;++i) tmp.pb(h[ch[x][1]][i]);
sort(tmp.begin(),tmp.end());
int top=0;
sz=tmp.size();
for (int i=0;i<sz;++i){
while (top>1&&cross(tmp[i]-st[top-1],st[top]-st[top-1])>=0) --top;
st[++top]=tmp[i];
}
h[x].clear(); p[x]=0;
for (int i=1;i<=top;++i) h[x].pb(st[i]);
}
void _modify(int x,int d,int lx,int rx,Dot &delta,int dep){
if (lx==rx){h[x].pb(delta); return;}
++cnt[x];
int mid=lx+rx>>1;
if (d<=mid) _modify(ch[x][0],d,lx,mid,delta,dep+1);
else _modify(ch[x][1],d,mid+1,rx,delta,dep+1);
if (cnt[x]==rx-lx+1){
if (full[dep]){
//if (full[dep]==13) printf("+33\n");
get_hull(full[dep]);
}
full[dep]=x;
}
}
void modify(int d,Dot delta){_modify(1,d,1,n,delta,1);}
ll get_val(int x,ll suf){
/*int l=0,r=h[x].size()-1,mid,ret=r;
if (h[x].size()==1){
return h[x][0].x*suf+h[x][0].y;
}
while (l<=r){
mid=l+r>>1;
if (h[x][mid].y-h[x][mid+1].y>=-suf*(h[x][mid].x-h[x][mid+1].x)) ret=mid,l=mid+1;
else r=mid-1;
}
return h[x][ret].x*suf+h[x][ret].y;*/
int sz=h[x].size()-1;
while (p[x]<sz&&h[x][p[x]].x*suf+h[x][p[x]].y>h[x][p[x]+1].x*suf+h[x][p[x]+1].y) ++p[x];
return h[x][p[x]].x*suf+h[x][p[x]].y;
}
ll _query(int x,int l,int r,int lx,int rx,ll suf){
if (l<=lx&&rx<=r&&h[x].size()) return get_val(x,suf);
int mid=lx+rx>>1;
if (r<=mid) return _query(ch[x][0],l,r,lx,mid,suf);
else if (l>mid) return _query(ch[x][1],l,r,mid+1,rx,suf);
else return min(_query(ch[x][0],l,mid,lx,mid,suf),_query(ch[x][1],mid+1,r,mid+1,rx,suf));
}
ll query(int l,int r,ll suf){return l>r?inf:_query(1,l,r,1,n,suf);}
void _del(int x,int d,int lx,int rx,int dep){
h[x].clear();
if (lx==rx) return;
--cnt[x];
if (full[dep]==x) full[dep]=0;
int mid=lx+rx>>1;
if (d<=mid) _del(ch[x][0],d,lx,mid,dep+1);
else _del(ch[x][1],d,mid+1,rx,dep+1);
}
void del(int d){_del(1,d,1,n,1);}
}/*}}}*/
int l[N],t[N],w[N],st[N];
ll suf[N],f[N],mn[N][TOP+1];
int n,m;
void init(){
suf[n+1]=0;
for (int i=n;i>=1;--i)
suf[i]=suf[i+1]+w[i];
Seg::build(n);
}
ll get_mn(int l,int r){//min_f
int len=r-l+1,lg=(int)(log(1.0*len)/log(2.0));
return min(mn[r][lg],mn[l+(1<<lg)-1][lg]);
}
void solve(){
int top=0,p;
f[0]=0;
st[++top]=0;
for (int i=1;i<=n;++i){
while (top&&t[st[top]]<=t[i])
Seg::del(top),--top;
st[++top]=i;
//printf("#%d\n",top);
Seg::modify(top,Dot(t[i],get_mn(st[top-1],i-1)));
p=upper_bound(st+1,st+1+top,l[i])-st;
f[i]=1LL*t[st[p]]*suf[i+1]+get_mn(l[i],st[p]-1);
f[i]=min(f[i],Seg::query(p+1,top,suf[i+1]));
mn[i][0]=f[i];//(i-(1<<j)+1)~i
for (int j=1;i-(1<<j-1)>=1;++j) mn[i][j]=min(mn[i][j-1],mn[i-(1<<j-1)][j-1]);
}
printf("%lld\n",f[n]);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d%d%d",l+i,t+i,w+i);
init();
solve();
}