斜率优化(李超线段树)

虽然我现在还不怎么理解
但是
这个和这个
都相当于是斜率优化的

板子

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10,M=1e9;
const ll INF=1e18;
int read(){
	int x;scanf("%d",&x);return x;
}
int n,cnt,rt;
ll h[N],w[N],sum[N],f[N];
struct{
	ll k,b;
}s[N];
struct{
	int lc,rc,c;
}t[N*20];
ll calc(int id,ll x){
	return s[id].k*x+s[id].b;
}
void update(int &p,ll l,ll r,int id){
	if(!p) p=++cnt;
	if(!t[p].c) return t[p].c=id,void();
	ll mid=(l+r)>>1;
	if(calc(id,mid)<calc(t[p].c,mid)) swap(id,t[p].c);
	if(calc(id,l)<calc(t[p].c,l)) update(t[p].lc,l,mid,id);
	else if(calc(id,r)<calc(t[p].c,r)) update(t[p].rc,mid+1,r,id);
}
ll ask(int p,ll l,ll r,ll x){
	if(!p) return INF;
	ll res=calc(t[p].c,x);
	if(l==r) return res;
	ll mid=(l+r)>>1;
	if(x<=mid) return min(res,ask(t[p].lc,l,mid,x));
	return min(res,ask(t[p].rc,mid+1,r,x));
}
int main(){
	n=read();
	for(int i=1;i<=n;i++) h[i]=read();
	for(int i=1;i<=n;i++){
		w[i]=read();
		sum[i]=sum[i-1]+w[i];
	}
	s[1]={-2*h[1],f[1]+h[1]*h[1]-sum[1]};
	update(rt,-M,M,1);
	for(int i=2;i<=n;i++){
		f[i]=ask(rt,-M,M,h[i])+h[i]*h[i]+sum[i-1];
		s[i]={-2*h[i],f[i]+h[i]*h[i]-sum[i]};
		update(rt,-M,M,i);
	}
	printf("%lld",f[n]);
	return 0;
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10,M=1e9;
ll read(){
	ll x;scanf("%lld",&x);return x;
}
struct{
	ll k,b;
}s[N];
ll h[N],f[N];
struct{
	int lc,rc,v;
}t[N];
ll calc(int id,ll x){
	return s[id].k*x+s[id].b;
}
int cnt,rt;
void update(int &p,ll l,ll r,int id){
	if(!p) p=++cnt;
	if(!t[p].v) return t[p].v=id,void();
	int mid=l+r>>1;
	if(calc(id,mid)<calc(t[p].v,mid)) swap(id,t[p].v);
	if(calc(id,l)<calc(t[p].v,l)) update(t[p].lc,l,mid,id);
	else if(calc(id,r)<calc(t[p].v,r)) update(t[p].rc,mid+1,r,id);
}
ll ask(int p,ll l,ll r,ll x){
	if(!p) return 1e18;
	ll ans=calc(t[p].v,x);
	if(l==r) return ans;
	ll mid=l+r>>1;
	if(x<=mid) return min(ans,ask(t[p].lc,l,mid,x));
	return min(ans,ask(t[p].rc,mid+1,r,x));
}
int main(){
	int	n=read();
	ll c=read();
	for(int i=1;i<=n;i++) h[i]=read();
	s[1]={-2*h[1],h[1]*h[1]+f[1]};
	update(rt,-M,M,1);
	for(int i=2;i<=n;i++){
		f[i]=ask(rt,-M,M,h[i])+h[i]*h[i]+c;
		s[i]={-2*h[i],h[i]*h[i]+f[i]};
		update(rt,-M,M,i);
	}
	printf("%lld",f[n]);
	return 0;
}

代码也是基本一样,第一篇是自己推+抄了李超,第二篇用的第一篇模板,然后自己改了改dp转移

(2025/10/9)遇到了一道斜率优化题

首先有一个朴素的\(n^4\)的做法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2010;
const ll INF=1e18;
int read(){
	int x;scanf("%d",&x);return x;
}
ll r[N],f[10][N];
ll calc(int l,int r){
	ll res=0;
	for(int i=l;i<=r;i++)
		res+=::r[i]*(i-l);
	return res;
}
ll calc2(int l,int r,int p){
	ll res=0;
	for(int i=l;i<=r;i++)
		res+=::r[i]*(i-p);
	return res;
}
int main(){
	int n=read(),k=read();
	for(int i=1;i<=n;i++) r[i]=read(),r[i+n]=r[i];
	ll ans=INF;
	for(int t=1;t<=n;t++){//断环成链 
		for(int i=0;i<=k;i++)
			for(int j=t;j<=t+n-1;j++)
				f[i][j]=INF;
		for(int j=t;j<=t+n-1;j++) f[1][j]=calc(t,j);
		for(int i=2;i<=k;i++){
			for(int j=t+i-1;j<=t+n-1;j++){
				//f[i][j]表示在j位置开了第i扇门的最小花费
				for(int p=t+i-2;p<j;p++){
					f[i][j]=min(f[i][j],f[i-1][p]+calc(p+1,j));
				}
			}
		}
		for(int j=t+k-1;j<=t+n-1;j++){
			if(f[k][j]<INF){
				ll res=f[k][j];
				if(j<t+n-1) res+=calc2(j+1,t+n-1,t);
				ans=min(ans,res);
			}
		}
	}
//	ll ans=INF;
//	for(int i=k;i<2*n;i++) ans=min(ans,f[k][i]);
	printf("%lld",ans);
	return 0;
}

这个可以拿下30分的高分,并且可以通过这道题的弱化版
这时候我们注意到calc里的内容可以进行前缀和优化,那么我们就可以优化掉一个n,即有了\(n^3\)的做法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2010;
const ll INF=1e18;
int read(){
	int x;scanf("%d",&x);return x;
}
ll r[N],f[10][N];
ll sum1[N],sum2[N];
ll calc(int l,int r){
//	ll res=0;
//	for(int i=l;i<=r;i++)
//		res+=::r[i]*(i-l);//res+=r[i]*i-r[i]*l=sum2[i]-sum1[i]*l;
//	return res;
	return (sum2[r]-sum2[l-1])-l*(sum1[r]-sum1[l-1]);
}
ll calc2(int l,int r,int p){
//	ll res=0;
//	for(int i=l;i<=r;i++)
//		res+=::r[i]*(i-p);
//	return res;
	return (sum2[r]-sum2[l-1])-p*(sum1[r]-sum1[l-1]);
}
int main(){
	int n=read(),k=read();
	for(int i=1;i<=n;i++) r[i]=read(),r[i+n]=r[i];
	for(int i=1;i<=2*n;i++){
		sum1[i]=sum1[i-1]+r[i];
		sum2[i]=sum2[i-1]+i*r[i];
	}
	ll ans=INF;
	for(int t=1;t<=n;t++){//断环成链 
		for(int i=0;i<=k;i++)
			for(int j=t;j<=t+n-1;j++)
				f[i][j]=INF;
		for(int j=t;j<=t+n-1;j++) f[1][j]=calc(t,j);
		for(int i=2;i<=k;i++){
			for(int j=t+i-1;j<=t+n-1;j++){
				//f[i][j]表示在j位置开了第i扇门的最小花费
				for(int p=t+i-2;p<j;p++){
					f[i][j]=min(f[i][j],f[i-1][p]+calc(p+1,j));
				}
			}
		}
		for(int j=t+k-1;j<=t+n-1;j++){
			if(f[k][j]<INF){
				ll res=f[k][j];
				if(j<t+n-1) res+=calc2(j+1,t+n-1,t);
				ans=min(ans,res);
			}
		}
	}
//	ll ans=INF;
//	for(int i=k;i<2*n;i++) ans=min(ans,f[k][i]);
	printf("%lld",ans);
	return 0;
}

这时候就有了60分的高分
那么,我们还能怎样优化呢?
注意到,我们可以把转移式展开!
我们有转移方程:

\[f[i][j] = \min_{p} \{ f[i-1][p] + \text{calc}(p+1, j) \} \]

其中:

\[\text{calc}(L, R) = \sum_{x=L}^R (x-L) \cdot r[x] = \sum_{x=L}^R x\cdot r[x] - L \cdot \sum_{x=L}^R r[x] \]

把 \(\text{calc}(p+1, j)\) 展开:

\[\text{calc}(p+1, j) = \text{sum2}[j] - \text{sum2}[p] - (p+1) \cdot [\text{sum1}[j] - \text{sum1}[p]] \]

代入转移方程:

\[f[i][j] = \min_p \{ f[i-1][p] + \text{sum2}[j] - \text{sum2}[p] - (p+1)\cdot\text{sum1}[j] + (p+1)\cdot\text{sum1}[p] \} \]

提取与 \(p\) 无关的项:

\[f[i][j] = \text{sum2}[j] + \min_p \{ f[i-1][p] - \text{sum2}[p] + (p+1)\cdot\text{sum1}[p] - (p+1)\cdot\text{sum1}[j] \} \]

设:

\[B_p = f[i-1][p] - \text{sum2}[p] + (p+1)\cdot\text{sum1}[p] \]

\[K_p = -(p+1) \]

\[X_j = \text{sum1}[j] \]

则:

\[f[i][j] = \text{sum2}[j] + \min_p \{ B_p + K_p \cdot X_j \} \]

这就是标准的斜率优化形式:\(\min\{K_p \cdot X + B_p\}\)。
此时,只需翻出来上面的板子,进行一一对应即可通过本题

//T了五六发,感觉被资本做局了
//结果是M开的太大了小一点直接快了1倍 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2010;
const ll INF=1e18;
int read(){
	int x;scanf("%d",&x);return x;
}
struct{
	ll k,b;
}s[N];
struct{
	int l,r,c;
}t[N*20];
ll calc(int id,ll x){
	return s[id].k*x+s[id].b;
}
int cnt,rt;
ll M;
void update(int &p,ll l,ll r,int id){
	if(!p) p=++cnt;
	if(!t[p].c) return t[p].c=id,void();
	ll mid=(l+r)>>1;
	if(calc(id,mid)<calc(t[p].c,mid)) swap(id,t[p].c);
	if(calc(id,l)<calc(t[p].c,l)) update(t[p].l,l,mid,id);
	else if(calc(id,r)<calc(t[p].c,r)) update(t[p].r,mid+1,r,id);
}
ll ask(int p,ll l,ll r,ll x){
	if(!p) return LONG_MAX;
	ll ans=calc(t[p].c,x);
	if(l==r) return ans;
	ll mid=l+r>>1;
	if(x<=mid) return min(ans,ask(t[p].l,l,mid,x));
	return min(ans,ask(t[p].r,mid+1,r,x));
}
void clear(){
	for(int i=1;i<=cnt;i++)
		t[i].l=t[i].r=t[i].c=0;
		cnt=rt=0;
}
ll r[N],f[10][N];
ll sum1[N],sum2[N];
//ll calc(int l,int r){
////	ll res=0;
////	for(int i=l;i<=r;i++)
////		res+=::r[i]*(i-l);//res+=r[i]*i-r[i]*l=sum2[i]-sum1[i]*l;
////	return res;
//	return (sum2[r]-sum2[l-1])-l*(sum1[r]-sum1[l-1]);
//}
ll calc2(int l,int r,int p){
//	ll res=0;
//	for(int i=l;i<=r;i++)
//		res+=::r[i]*(i-p);
//	return res;
	return (sum2[r]-sum2[l-1])-p*(sum1[r]-sum1[l-1]);
}
int main(){
	int n=read(),k=read();
	for(int i=1;i<=n;i++) r[i]=read(),r[i+n]=r[i];
	for(int i=1;i<=2*n;i++){
		sum1[i]=sum1[i-1]+r[i];
		sum2[i]=sum2[i-1]+i*r[i];
		M=max(M,sum1[i]);
	}
	ll ans=INF;
	for(int t=1;t<=n;t++){//断环成链 
		for(int i=0;i<=k;i++)
			for(int j=t;j<=t+n-1;j++)
				f[i][j]=INF;
		for(int j=t;j<=t+n-1;j++)
			f[1][j]=(sum2[j]-sum2[t-1])-t*(sum1[j]-sum1[t-1]);/// f[1][j]=calc(t,j);
		for(int i=2;i<=k;i++){
			clear();
			for(int p=t+i-2;p<=t+n-1;p++){
				ll b=f[i-1][p]-sum2[p]+(p+1)*sum1[p];
				ll k=-(p+1);
				s[p]={k,b};
				update(rt,-M,M,p);
			}
			for(int j=t+i-1;j<=t+n-1;j++){
			//f[i][j]表示在j位置开了第i扇门的最小花费
//				for(int p=t+i-2;p<j;p++){
//					f[i][j]=min(f[i][j],f[i-1][p]+calc(p+1,j));
//				}
				ll val=ask(rt,-M,M,sum1[j])+sum2[j];
				f[i][j]=min(f[i][j],val);
			}
		}
		for(int j=t+k-1;j<=t+n-1;j++){
			if(f[k][j]<INF){
				ll res=f[k][j];
				if(j<t+n-1) res+=calc2(j+1,t+n-1,t);
				ans=min(ans,res);
			}
		}
	}
//	ll ans=INF;
//	for(int i=k;i<2*n;i++) ans=min(ans,f[k][i]);
	printf("%lld",ans);
	return 0;
}

更为简洁的写法:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2010;
const ll INF=1e18;
int read(){
	int x;scanf("%d",&x);return x;
}
struct{
	ll k,b;
}s[N];
struct{
	int l,r,c;
}t[N*20];
ll calc(int id,ll x){
	return s[id].k*x+s[id].b;
}
int cnt,rt;
void update(int &p,ll l,ll r,int id){
	if(!p) p=++cnt;
	if(!t[p].c) return t[p].c=id,void();
	ll mid=(l+r)>>1;
	if(calc(id,mid)<calc(t[p].c,mid)) swap(id,t[p].c);
	if(calc(id,l)<calc(t[p].c,l)) update(t[p].l,l,mid,id);
	else if(calc(id,r)<calc(t[p].c,r)) update(t[p].r,mid+1,r,id);
}
ll ask(int p,ll l,ll r,ll x){
	if(!p) return LONG_MAX;
	ll ans=calc(t[p].c,x);
	if(l==r) return ans;
	ll mid=l+r>>1;
	if(x<=mid) return min(ans,ask(t[p].l,l,mid,x));
	return min(ans,ask(t[p].r,mid+1,r,x));
}
void clear(){
	for(int i=1;i<=cnt;i++)
		t[i].l=t[i].r=t[i].c=0;
	cnt=rt=0;
}
//前面这套的都是李超树板子,一个字没改,看来这板子确实十分万能 
ll r[N],f[10][N];
ll sum1[N],sum2[N];
ll calc2(int l,int r,int p){
	return (sum2[r]-sum2[l-1])-p*(sum1[r]-sum1[l-1]);
}
int main(){
	int n=read(),k=read();
	for(int i=1;i<=n;i++) r[i]=read(),r[i+n]=r[i];
	ll M=0; 
	for(int i=1;i<=2*n;i++){
		sum1[i]=sum1[i-1]+r[i];
		sum2[i]=sum2[i-1]+i*r[i];
		M=max(M,sum1[i]);//求出最大的范围(千万不要太大,会直接影响到李超树的运行次数) 
	}
	ll ans=INF;
	for(int t=1;t<=n;t++){//断环成链 枚举第t个点为起点 
		for(int j=t;j<=t+n-1;j++)
			f[1][j]=(sum2[j]-sum2[t-1])-t*(sum1[j]-sum1[t-1]);//算出一扇门到达所有点的花费 
		for(int i=2;i<=k;i++){
			clear();//注意清空 
			for(int p=t+i-2;p<=t+n-1;p++){
				//经过拆解dp转移式可以得到的kb,具体过程可见博客\
		 		不过其实写在这里也不是不行 \
		 		f[i][j]=min(f[i-1][p]+calc(p+1,j));\
		 			   =min(f[i-1][p]+sum2[j]-sum2[p]-(p+1)*(sum1[j]-sum1[p]);\
		 			   =sum2[j]+min(f[i-1][p]-sum2[p]+(p+1)*sum1[p]-(p+1)*sum1[j]);
				ll b=f[i-1][p]-sum2[p]+(p+1)*sum1[p];
				ll k=-(p+1);
				//你看,我的b是不是就是min的前半部分,x是不是就是sum1[j],k是不是就是x的系数 
				s[p]={k,b};
				update(rt,-M,M,p);
				//把这些直线都存在线段树里 
			}
			for(int j=t+i-1;j<=t+n-1;j++)
				//f[i][j]表示在j位置开了第i扇门的最小花费
				f[i][j]=ask(rt,-M,M,sum1[j])+sum2[j];
				//ask相当于y,加上的是一个与p无关的常数,上文也提到它了 
		}
		for(int j=t+k-1;j<=t+n-1;j++){
			if(f[k][j]<INF){
				ll res=f[k][j];
				if(j<t+n-1) res+=calc2(j+1,t+n-1,t);
				ans=min(ans,res);
			}
		}//这一段暴力里面也会有,和前面的预处理一样,因为复杂度本来就是n^2,所以也没什么可优化的了 
	}
	printf("%lld",ans);
	return 0;
}

不过话又说回来了,这道题其实还可以决策单调性或是分治,而且本来今天(2025/10/9)的专题就是分治,所以还要对其他做法进行进一步的了解.

posted @ 2025-07-31 15:28  追旭  阅读(28)  评论(0)    收藏  举报
//雪花飘落效果