斜率优化

斜率优化

转自:

https://www.cnblogs.com/MashiroSky/p/6009685.html

例1:任务安排1

蓝书或下面解答

https://www.luogu.com.cn/blog/ButterflyDew/solution-p2365

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline int read() {
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f; 
}
const int N=1e5+10;
int n,s,f[N],q[N];
int t[N],c[N];

int main() {
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    n=read();s=read();
    for(int i=1,tt,cc;i<=n;i++) {
        tt=read();cc=read();
        t[i]=t[i-1]+tt;
        c[i]=c[i-1]+cc;
    }
    int l=1,r=1;
    q[1]=0;
    for(int i=1;i<=n;i++) {
        while(l<r && (f[q[l+1]]-f[q[l]])<=(t[i]+s)*(c[q[l+1]]-c[q[l]])) l++;
        f[i]=f[q[l]]-(s+t[i])*c[q[l]]+t[i]*c[i]+s*c[n];
        while(l<r && (f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])>=(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]])) r--;
        q[++r]=i;
    }
    printf("%d\n",f[n]);
    return 0;
}

例2:任务安排2

这里T可能是负数,所以斜率不单调了,我们单调队列就不能只维护相邻两点线段的斜率了,我们要维护整个凸壳,然后二分查找出一个位置P,P左边线段斜率<S+c[i] ,P右端线段斜率>S+c[i]

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define int long long
inline int read() {
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f; 
}
const int N=300010;
int n,s,f[N],q[N];
int t[N],c[N];
int l,r;
int search(int i,int k) {
	if(l==r) return q[l];
	int L=l,R=r;
	while(L<R) {
		int mid=(L+R)>>1;
		if(f[q[mid+1]]-f[q[mid]]<=k*(c[q[mid+1]]-c[q[mid]])) L=mid+1;
		else R=mid;
	}
	return q[L];
}
signed main() {
    n=read();s=read();
    for(int i=1,tt,cc;i<=n;i++) {
        tt=read();cc=read();
        t[i]=t[i-1]+tt;
        c[i]=c[i-1]+cc;
    }
	memset(f,0x3f,sizeof(f));
    f[0]=0;
    l=1,r=1;
    q[1]=0;
    for(int i=1;i<=n;i++) {
		int p=search(i,s+t[i]);
       f[i]=f[p]-(s+t[i])*c[p]+t[i]*c[i]+s*c[n];
        while(l<r && (f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])>=(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]])) r--;
        q[++r]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}

例3:Cats Transport

例4:玩具装箱

https://www.luogu.com.cn/problem/P3195

\[f[i]=min(f[j]+(i-j+sum[i]-sum[j]-L-1)^2),(j<i)\\ 移项得=> f[i]=min(f[j]+((i+sum[i])-(j+sum[j]+L+1))^2)\\ 令 a[i]=i+sum[i],b[j]=j+sum[j]+L+1;\\ 则 f[i]=f[j]+a[i]^2+b[j]^2-2*a[i]*b[j];\\ 移项\quad2*a[i]*b[j]+f[i]-a[i]^2=f[j]+b[j]^2;\\ \]

​ ----k--- -b- ----x-- -------y-------

#include <iostream>
#include <cstdio>
using namespace std;
typedef double db;
typedef long long LL;
const int N=50010;
int n,L;
db c[N],sum[N],f[N];
int h,t,q[N];
inline db a(int i){return sum[i]+i;}
inline db b(int i){return a(i)+L+1;}
inline db X(int i){return b(i);}
inline db Y(int i){return f[i]+b(i)*b(i);}
inline db slope(int i,int j){return (Y(i)-Y(j))/(X(i)-X(j));}
int main(){
    scanf("%d%d",&n,&L);
    for(int i=1;i<=n;i++){
       	scanf("%lf",&c[i]);
		sum[i]=sum[i-1]+c[i];
    }
    h=1,t=1;
    for(int i=1;i<=n;i++){
        while(h<t&&slope(q[h],q[h+1])<2*a(i)) ++h;//
        f[i]=f[q[h]]+(a(i)-b(q[h]))*(a(i)-b(q[h]));
        while(h<t&&slope(i,q[t-1])<slope(q[t-1],q[t])) --t;//q[t]在凸包内,肯定不是最优了
        q[++t]=i;
    }
    printf("%lld\n",(LL)f[n]);
    return 0;
}

例5:特别行动队

$ f[i]=f[j]+a(sum[i]-sum[j-1])^2+b(sum[i]-sum[j-1])+c $

拆了移项得:$ 2asum[i]sum[j-1]+f[i]=f[j]+asum[j-1]^2-b*sum[j-1] $

同理套板子

#include <cstdio>
#include <iostream>
using namespace std;
typedef double db;
typedef long long LL;	
inline int read() {
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f; 
}
const int N=1e6+10;
int n,t,h,q[N];
db f[N],c,b,a,sum[N],dat; 
inline db x(int i){return sum[i];}
inline db y(int i){return f[i]+a*sum[i]*sum[i]-b*sum[i];}
inline db k(int i,int j){return (y(i)-y(j))/(x(i)-x(j));}
int main(){
	n=read();scanf("%lf%lf%lf",&a,&b,&c);
	for(int i=1;i<=n;i++) scanf("%lf",&dat),sum[i]=sum[i-1]+dat;
	h=1;t=1;
	for(int i=1;i<=n;i++){
		while(h<t&&k(q[h],q[h+1])>2*a*sum[i]) h++;
		int j=q[h];f[i]=f[j]+a*(sum[i]-sum[j])*(sum[i]-sum[j])+b*(sum[i]-sum[j])+c;
		while(h<t&&k(i,q[t-1])>k(q[t-1],q[t])) t--;
		q[++t]=i;
	}
	printf("%lld\n",(LL)f[n]);
	return 0;
}

例6:序列分割

https://www.luogu.com.cn/blog/hongzy/solution-p3648

答案与切的顺序无关

第 k 次分割,第 i 个元素

$ f[k][i]=max(f[k-1][j]+(sum[i]-sum[j])*sum[j])$

#include <iostream>
#include <stack>
#include <cstdio>
typedef long long LL;
using namespace std;
int n,m,j,q[100005],to[205][100005],d;
LL sum[100005];
stack<int> s;
long double f[2][100005];
long double X(int x){return sum[x];}
long double Y(int x){return sum[x]*sum[x]-f[1-d][x];}
long double k(int x,int y){
   if(X(x)==X(y)) return -1e18;
   return (Y(x)-Y(y))/(X(x)-X(y));
}
int main(){
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;i++) scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
   for(j=1;j<=m;j++){
   	int h=1,t=1;
   	for(int i=j;i<=n;i++){
   		while(h<t&&k(q[h],q[h+1])<sum[i]) h++;
   		f[d][i]=f[1-d][q[h]]+sum[q[h]]*(sum[i]-sum[q[h]]);
   		to[j][i]=q[h];
   		while(h<t&&k(q[t],q[t-1])>k(q[t-1],i)) t--;
   		q[++t]=i;
   	}
       d=1-d;
   }
   printf("%lld\n",(long long)f[1-d][n]);
   for(int i=m,u=n;i>=1;i--){
   	u=to[i][u];
   	s.push(u);
   }
   while(!s.empty()){
   	int x=s.top();
   	cout<<x<<" ";
   	s.pop();
   }
   return 0;
}

例7:仓库建设

设 f(i)当前到 i位置,在该位置建仓库的答案

\[f[i]=min(~f[j]+c[i]+\sum_{k=j+1}^{i} (p[k]*(x[i]-x[k])~)\\ f[i]=f[j]+c[i]+\sum_{k=j+1}^{i} (p[k]*x[i]-p[k]*x[k])~)\\ 设前缀和\sum p=sum1[],\sum p*x=sum2[]\\ 接着化简\\ f[i]=f[j]+c[i]+x[i]*(sum1[i]-sum1[j])-sum2[i]+sum2[j]~)\\ f[j]+sum2[j]=sum1[j]*x[i]-x[i]*sum1[i]-c[i]+f[i]+sum2[i];\\ \]

​ y x k b

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
typedef double db;
const int N=1e6+10;
int n,q[N];
db x[N],p[N],c[N],sum1[N],sum2[N],f[N];
inline db X(int i) {return sum1[i];}
inline db Y(int i) {return f[i]+sum2[i];}
inline db k(int i,int j) {return (Y(i)-Y(j))/(X(i)-X(j));}

int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%lf%lf%lf",&x[i],&p[i],&c[i]);
        sum1[i]=sum1[i-1]+p[i];
        sum2[i]=sum2[i-1]+p[i]*x[i];
    }
    int h=1,t=1;
    for(int i=1;i<=n;i++) {
        while(h<t && k(q[h],q[h+1])<x[i]) h++;
        f[i]=f[q[h]]+c[i]+x[i]*(sum1[i]-sum1[q[h]])-sum2[i]+sum2[q[h]];
        while(h<t && k(q[t],q[t-1])>k(i,q[t-1])) t--;
        q[++t]=i;
    }
    printf("%lld",(long long)f[n]);
    return 0;
}

例8:锯木厂选址

正解是利用容斥原理,用总花费减去省下来的花费

设路程后缀和 d(i),重量前缀和 s(i),直接全部树到山脚花费sum ,第一个工厂 j ,第二个工厂 i

则 $ ans=sum-d(j)s(j)-d(i)(s(i)-s(j))$

$ d(j)s(j)=d(i)s(j)+sum-d(i)*s(i)$

y k x b

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
typedef double db;
const int N=1e6+10;
int n,q[N];
db w[N],x[N],d[N],s[N],sum,ans=0x3f3f3f3f;
inline db k(int i,int j) {return (d[i]*s[i]-d[j]*s[j])/(s[i]-s[j]);}
    int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%lf%lf",&w[i],&x[i]);
        s[i]=s[i-1]+w[i];
    }
    for(int i=n;i>=1;i--)   
        d[i]=d[i+1]+x[i];
    for(int i=1;i<=n;i++)
        sum+=w[i]*d[i];
    int h=1,t=1;
    for(int i=1;i<=n;i++) {
        while(h<t && k(q[h],q[h+1])>d[i]) h++;
        ans=min(ans,sum-d[q[h]]*s[q[h]]-d[i]*(s[i]-s[q[h]]));
        while(h<t && k(q[t-1],q[t])<k(q[t],i)) t--;
        q[++t]=i;
    }
    printf("%lld",(long long)ans);
    return 0;
}

例9:征途

啊?方差???

例10:Land Acquisition G

posted @ 2020-08-17 20:57  ke_xin  阅读(52)  评论(0编辑  收藏  举报