斜率优化
斜率优化
转自:
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
----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位置,在该位置建仓库的答案
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:征途
啊?方差???