李超线段树优化动态规划

3.李超线段树优化动态规划

3.1 李超线段树优化动态规划的基本方法

数据结构中先搞 李超线段树 的做法,因为代码短常数小。或者可以上 OI-WIKI 学习,它那个应该讲得比我好,而且还有图。
李超线段树适用于优化一些 \(1D / 1D\) 型的动态规划,而且它们的转移方程形似 \(f_i=p(\max or \min\{r(j)\times q(i)+s(j)\})\),其中 \(p(x)\) 是关于 \(x\) 的一次函数,\(q(i)\)\(r(j)、s(j)\) 分别是关于 \(i\)\(j\) 的函数。
普遍做法是对于每个 \(i\) ,查询当前 \(x=q(i)\) 的纵坐标的最大值或最小值,然后将括号内的东西视为直线 \(y=r(j)\times x+s(j)\) 扔进李超线段树。
时间复杂度 \(O(n\log n)\)

3.2 例题

例题3-1:[CEOI2017] Building Bridges

这一题可以设计状态:\(f_i=\min\limits_{0\le j<i}\Big\{f_j+(h_i-h_i)^2+h_j^2+s_{i-1}-s_j\Big\}\)
拆开得到 \(f_j+h_j^2-s_j=2h_i\times h_j+f_i-h_i^2-s_{i-1}\)
发现 \(f_j\) 不单调,无法直接维护凸包。
一个直观的想法是用平衡树维护,查询时在平衡树上二分。但是我不想写平衡树。
给出一个码量和时间复杂度都很小的李超线段树做法。
将式子变成 \(f_i=h_i^2+s_{i-1}+\min\limits_{0\le j<i}\Big\{-2h_j\times h_i+f_j+h_j^2-s_j\Big\}\)
那么 \(\min\) 里的部分就可以看成对于每个 \(j\) 加入一条 \( k=-2h_j\)\(b=f_j+h_j^2-s_j\) 的直线,并查询在 \(h_i\) 处最小的值。
这就是李超线段树的模板了,由于不用确定大区间,所以时间复杂度 \(O(n\log n)\)
注意这里是求最小值,所以要将编号为 \(0\) 的直线的截距 \(b\) 初始化为无穷大。

点击查看代码
struct _{ll k,b;}p[100010];
ll s[4000040],h[100010],w[100010],f[100010],n,c=0;
inline ll cal(ll x,ll v){return p[x].k*v+p[x].b;}
void upd(ll x,ll l,ll r,ll u){
	ll &v=s[x],mid=(l+r)>>1;
	if(cal(u,mid)<cal(v,mid))swap(u,v);
	if(cal(u,l)<cal(v,l))upd(x<<1,l,mid,u);
	if(cal(u,r)<cal(v,r))upd(x<<1|1,mid+1,r,u);
}
ll query(ll x,ll l,ll r,ll v){
	if(l==r)return cal(s[x],v);
	ll mid=(l+r)>>1;
	if(v<=mid)return min(cal(s[x],v),query(x<<1,l,mid,v));
	else return min(cal(s[x],v),query(x<<1|1,mid+1,r,v));
}
int main(){
	n=read();p[0].b=1e18;
	for(ll i=1;i<=n;i++)h[i]=read();
	for(ll i=1;i<=n;i++)w[i]=w[i-1]+read();
	p[1]=_{-2*h[1],f[1]+h[1]*h[1]-w[1]};upd(1,0,1000001,1);
	for(ll i=2;i<=n;i++){
		f[i]=h[i]*h[i]+w[i-1]+query(1,0,1000001,h[i]);
		p[i]=_{-2*h[i],f[i]+h[i]*h[i]-w[i]};upd(1,0,1000001,i);
	}
	cout<<f[n]<<'\n';
	return 0;
}

例题3-3:[NOI2007] 货币兑换

这一题也可以用李超线段树做。首先我们可以发现每次操作要将金券卖完或是将钱花完买金券,而且每天可以操作无数次,那我们就可以把每一天结束的最大钱数记录下来,然后再记录它全部拿来买金券可以各卖多少个,然后转移就行了。
具体转移:每一天你可以不操作,所以 \(f_i=\max(f_i,f_{i-1})\)。或者是你可以再第 \(j\) 天将所有钱换成金券,然后一直等到第 \(i\) 天才操作,将所有金券换成人民币,所以 \(f_i=\max\limits_{1\le j<i}\{x_j\times a_i+y_j\times b_i\}\)。然后发现跟 \(i\)\(j\) 都有关的项有两个怎么办?可以将它改成 \(f_i=b_i\times\max\limits_{1\le j<i}\{x_j\times c_i+y_j\}\),其中 \(c_i=\frac{a_i}{b_i}\)
然后问题就变成了加入直线 \(k=x_j\)\(b=y_j\),然后查询每个 \(c_i\) 处的最大值。但是我们发现 \(c_i\) 不是整数怎么办?我们发现原本的李超线段树要求整点查询只是为了方便分区间,具体的值反而没有那么重要。所以我们可以将所有 \(c_i\) 离散化一下,这样不会影响 \(c_i\) 之间的大小关系,也不会影响答案,计算时用真实的 \(c_i\) 就好了,可以用李超线段树维护。时间复杂度 \(O(n\log n)\)

点击查看代码
struct _{double k,b;}p[100010];
int n,s[400010];
double f[100010],a[100010],b[100010],c[100010],d[100010],r[100010];
inline int cmp(double x,double y){
	if(x-y>eps)return 1;
	if(y-x>eps)return -1;
	return 0;
}
inline double cal(int x,int v){return c[v]*p[x].k+p[x].b;}
void upd(int x,int l,int r,int u){
	int &v=s[x],mid=(l+r)>>1;
	if(cal(u,mid)>cal(v,mid))swap(u,v);
	if(cmp(cal(u,l),cal(v,l))==1)upd(x<<1,l,mid,u);
	if(cmp(cal(u,r),cal(v,r))==1)upd(x<<1|1,mid+1,r,u);
}
double query(int x,int l,int r,int v){
	if(l==r)return cal(s[x],v);
	int mid=(l+r)>>1;
	if(v<=mid)return max(cal(s[x],v),query(x<<1,l,mid,v));
	else return max(cal(s[x],v),query(x<<1|1,mid+1,r,v));
}
int main(){
	n=read();scanf("%lf",&f[0]);
	for(int i=1;i<=n;i++){scanf("%lf%lf%lf",&a[i],&b[i],&r[i]);d[i]=c[i]=a[i]/b[i];}
	sort(c+1,c+n+1);
	for(int i=1;i<=n;i++){
		f[i]=max(f[i-1],b[i]*query(1,1,n,lower_bound(c+1,c+n+1,d[i])-c));
		double g=a[i]*r[i]+b[i];p[i]=_{f[i]*r[i]/g,f[i]/g};upd(1,1,n,i);
	}
	printf("%.3f\n",f[n]);
	return 0;
}

3.2习题

习题3-1:[SDOI2012]基站建设

点击查看代码
struct _{double k,b;}p[500010];
ll n,m,s[2000010],a[500010],v[500010],r[500010];
double f[500010],ans=1e18;
inline ll cmp(double x,double y){
	if(x-y>eps)return 1;
	if(y-x>eps)return -1;
	return 0;
}
inline double cal(ll x,ll v){return p[x].k*a[v]+p[x].b;}
void upd(ll x,ll l,ll r,ll u){
	ll &v=s[x],mid=(l+r)>>1;
	if(cmp(cal(u,mid),cal(v,mid))<0)swap(u,v);
	if(cmp(cal(u,l),cal(v,l))<0)upd(x<<1,l,mid,u);
	if(cmp(cal(u,r),cal(v,r))<0)upd(x<<1|1,mid+1,r,u);
}
double query(ll x,ll l,ll r,ll v){
	if(l==r)return cal(s[x],v);
	ll mid=(l+r)>>1;
	if(v<=mid)return min(cal(s[x],v),query(x<<1,l,mid,v));
	else return min(cal(s[x],v),query(x<<1|1,mid+1,r,v));
}
int main(){
	scanf("%lld%lld",&n,&m);p[0].b=1e18;
	for(ll i=1;i<=n;i++){
		scanf("%lld%lld%lld",&a[i],&r[i],&v[i]);
		p[i]=_{1.0/(sqrt(r[i])*2),-a[i]/(sqrt(r[i])*2)};
	}
	p[1].b+=(f[1]=v[1]);upd(1,1,n,1);
	for(ll i=2;i<=n;i++){p[i].b+=(f[i]=v[i]+query(1,1,n,i));upd(1,1,n,i);}
	for(ll i=1;i<=n;i++)
		if(a[i]+r[i]>=m)ans=min(ans,f[i]);
	printf("%.3lf\n",ans);
	return 0;
}

习题3-2:P3571 [POI2014]SUP-Supercomputer

我写的题解

posted @ 2023-04-11 14:45  lrxQwQ  阅读(68)  评论(0)    收藏  举报