• 斜率优化


    斜率优化

    转自:

    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
    ",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
    ",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
    ",(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
    ",(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
    ",(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

  • 相关阅读:
    linux下安装qt-4.5_for_TQ210_V1.0.(TQ210)-ubuntu11.10过程出现的问题
    15+ 易响应的CSS框架快速开启你的敏捷网站项目
    分享10个超实用的jQuery代码片段
    免费数据库+免费空间建站
    25个令人难忘的广告设计
    android实现点击效果
    ServletContextListener
    ServletContext
    一个不错的地址--类序列化等
    Java的运行时数据存储机制
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13520022.html
Copyright © 2020-2023  润新知