zoukankan      html  css  js  c++  java
  • 斜率优化

    斜率优化

    转自:

    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

  • 相关阅读:
    【LeetCode】17. Letter Combinations of a Phone Number
    【LeetCode】16. 3Sum Closest
    【LeetCode】15. 3Sum 三个数和为0
    【LeetCode】14. Longest Common Prefix 最长前缀子串
    【LeetCode】13. Roman to Integer 罗马数字转整数
    【LeetCode】12. Integer to Roman 整型数转罗马数
    【LeetCode】11. Container With Most Water
    【LeetCode】10. Regular Expression Matching
    Models of good programmer
    RSA Algorithm
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13520022.html
Copyright © 2011-2022 走看看