上一期刚刚讲完斜率优化的原理,这一期我们就开始做题吧
(题目属于caioj)
因为我个人比较喜欢图像法,所以这些题目都是用图像法做的(这不是废话吗)
【问题描述】
有N个工厂,由高到底分布在一座山上。工厂1在山顶,工厂N在山脚。
L公司一般把产品直接堆放在露天,以节省费用。
突然有一天,被告知三天之后将有一场暴雨,于是公司决定紧急在某些工厂建立一些仓库以免产品被淋坏。
对于没有建立仓库的工厂,其产品应被运往其他的仓库进行储藏,
产品只能往山下运(即只能运往编号更大的工厂的仓库),
当然运送产品也是需要费用的,假设一件产品运送1个单位距离的费用是1。
假设建立的仓库容量都都是足够大的,可以容下所有的产品。
你将得到以下数据:
1、工厂i距离工厂1的距离Xi(其中X1=0,保证升序);
2、工厂i目前已有成品数量Pi;
3、在工厂i建立仓库的费用Ci;
请你帮助L公司寻找一个仓库建设的方案,使得总的费用(建造费用+运输费用)最小。
【输入文件】
第一行一个整数N,表示工厂的个数。
接下来N行每行包含两个整数Xi, Pi, Ci, 意义如题中所述。
【输出文件】
输出一行一个整数,为可以找到最优方案的费用。
【样例输入】
3
0 5 10
5 3 100
9 6 10
【样例输出】
32
【样例说明】
在工厂1和工厂3建立仓库,建立费用为10+10=20,运输费用为(9-5)*3 = 12,总费用32。
如果仅在工厂3建立仓库,建立费用为10,运输费用为(9-0)*5+(9-5)*3=57,总费用67,不如前者优。
【数据规模】
对于20%的数据, N ≤500;
对于40%的数据, N ≤10000;
对于100%的数据, N ≤1000000。
所有的Xi, Pi, Ci均在32位带符号整数以内,保证中间计算结果不超过64位带符号整数。
看完题以后,会发现需要O(N)的时间来算出两个地方之间的花费。因此,先思考怎么化简公式,来让状态转移变成O(1)的时间
先看一下,加入当前是i那么连续继承3个的价格是(这里主要是为了方便理解,所以可能不够严谨,请见谅)
p[1]*(x[i]-x[1]) + p[2]*(x[i]-x[2]) +p[3]*(x[i]-x[3])
把这个式子拆开,合并同类项得
( p[1]+p[2]+p[3] )*x[i] - p[1]*x[1] - p[2]*x[2] - p[3]*x[3]
然后我们令
s1[i] = p[1]+p[2]+...+p[i]
s2[i] = p[1]*x[1]+p[2]*x[2]+...+p[i]*x[i]
所以继承就变成
f[i] = f[j] + ( s1[i-1]-s1[j] )*x[i] - s2[i-1] + s2[j]
化成y = kx+b 的形式,得
f[j]+s2[j] = x[i]*s1[j] + f[i]+s1[i-1]*x[i]+s2[i-1]
y = f[j]+s2[j] (单调递增)
k = x[i] (单调递增)
x = s1[j] (单调递增)
b = f[i]+s1[i-1]*x[i]+s2[i-1]
显而易见,这一题也是维护下凸壳
代码
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std ;
inline int read() {
int x = 0 , f = 0 ; char s = getchar() ;
while ( !isdigit(s) ) f |= s=='-' , s = getchar() ;
while ( isdigit(s) ) x = (x<<1)+(x<<3)+(s-48) , s = getchar() ;
return f ? -x : x ;
}
typedef long long LL ;
const int N = 1e6 + 10 ;
LL n , x[N] , p[N] , c[N] ;
LL s1[N] , s2[N] ; LL f[N] ;
int head , tail , list[N] ;
inline LL X ( int j ) {
return s1[j] ;
}
inline LL Y ( int j ) {
return f[j]+s2[j] ;
}
inline double slope ( int j1 , int j2 ) {
return double((Y(j1)-Y(j2))/(X(j1)-X(j2))) ;
}
int main() {
/*
p[1]*(x[i]-x[1]) + p[2]*(x[i]-x[2]) +p[3]*(x[i]-x[3])
( p[1]+p[2]+p[3] )*x[i] - p[1]*x[1] - p[2]*x[2] - p[3]*x[3]
令 s1[i] = p[1]+p[2]+...+p[i]
s2[i] = p[1]*x[1]+p[2]*x[2]+...+p[i]*x[i]
f[i] = f[j] + ( s1[i-1]-s1[j] )*x[i] - s2[i-1] + s2[j]
f[i] = f[j] + s1[i-1]*x[i] - s1[j]*x[i] - s2[i-1] + s2[j]
f[j]+s2[j] = x[i]*s1[j] + f[i]+s1[i-1]*x[i]+s2[i-1]
y = f[j]+s2[j] (单调递增)
k = x[i] (单调递增)
x = s1[j] (单调递增)
b = f[i]+s1[i-1]*x[i]+s2[i-1]
*/
n = LL ( read() ) ;
for ( int i = 1 ; i <= n ; i ++ ) {
x[i] = LL(read()) ;
p[i] = LL(read()) ;
c[i] = LL(read()) ;
}
s1[0] = s2[0] = f[0] = 0 ;
for ( int i = 1 ; i <= n ; i ++ ) {
s1[i] = s1[i-1] + p[i] ;
s2[i] = s2[i-1] + p[i]*x[i] ;
}
head = tail = 1 ; list[1] = 0 ;
for ( int i = 1 ; i <= n ; i ++ ) {
while ( head<tail && slope(list[head],list[head+1])<x[i] ) head ++ ;
int j = list[head] ;
f[i] = f[j]+(s1[i-1]-s1[j])*x[i]-(s2[i-1]-s2[j]) + c[i] ;
while ( head<tail && slope(list[tail-1],list[tail])>slope(list[tail],i)) tail -- ;
list[++tail] = i ;
}
printf ( "%lld\n" , f[n] ) ; return 0 ;
}
【题目描述】
有N (1 <= N <= 50,000) 块长方形的土地。每块土地的长宽满足(1 <= 宽 <= 1,000,000; 1 <= 长 <= 1,000,000)。
每块土地的价格是它的面积,但FJ可以同时购买多块土地。这些土地的价格是它们最大的长乘以它们最大的宽, 但是土地的长宽不能交换。
如果FJ买一块3×5的地和一块5×3的地,则他需要付5×5=25。
FJ希望买下所有的土地,但是他发现分组来买这些土地可以节省经费。
他需要你帮助他找到最小的经费。
【输入格式】
第1行一个整数N。
下来N行。第i+1行包含两个数,分别为第i块土地的长和宽。
【输出格式】
求最小的可行费用。
【样例输入】
4
100 1
15 15
20 5
1 100
【样例输出】
500
【样例解释】
FJ分3组买这些土地:
第一组:100×1,
第二组1×100,
第三组20×5 和 15×15。
每组的价格分别为100,100,300, 总共500
来源/分类
Usaco2008 Mar
虽然这道题很难想到思路,但如果认真观察几分钟,就可以发现这个题目里面是有一个突破口的
假如我们有两块土地,分别是1X1和2X2,那么我们只需要买2X2的土地就可以了获得这两块土地了,1X1这块土地就是白送的
于是我们将土地的x为第一关键字,y为第二关键字将这些土地进行排序
然后我们就可以将免费的土地删去,以至于留下来的土地从1-n,x是单调递增,y是单调递减
假设f是i状态的最优的购买法,则
f[i] = f[j] + b[j+1].y*b[i].x
化成y=kx+b的形式,得
f[j] = -b[j+1].y*b[i].x + f[i]
y = f[j] ( 单调递增)
k = b[i].x (单调递增)
x = -b[j+1].y (单调递增)
b = f[i]
这一道题还是维护下凸壳
代码
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std ;
typedef long long LL ;
const int N = 5e4 + 10 ;
int n , tp ;
struct node {
LL x , y ;
} a[N] , b[N] ;
inline bool cmp ( node n1 , node n2 ) {
return n1.x==n2.x ? n1.y<n2.y : n1.x<n2.x ;
}
int l , r , q[N] ; LL f[N] ;
LL Y ( int j ) {
return f[j] ;
}
LL X ( int j ) {
return -b[j+1].y ;
}
double slop ( int i , int j ) {
return double(Y(j)-Y(i))/double(X(j)-X(i)) ;
}
int main() {
// freopen ( "1140.in" , "r" , stdin ) ;
// freopen ( "1140.out" , "w" , stdout ) ;
/*
f[i] = f[j] + b[j+1].y*b[i].x
f[j] = -b[j+1].y*b[i].x + f[i]
y = f[j] ( 单调递增)
k = b[i].x (单调递增)
x = -b[j+1].y (单调递增)
b = f[i]
*/
scanf ( "%d" , &n ) ;
for ( int i = 1 ; i <= n ; i ++ )
scanf ( "%lld%lld" , &a[i].x , &a[i].y ) ;
sort ( a+1 , a+n+1 , cmp ) ;
tp = 1 ; b[1] = a[1] ;
for ( int i = 2 ; i <= n ; i ++ ) {
while ( tp>0 && b[tp].y<=a[i].y ) tp -- ;
b[++tp] = a[i] ;
}
l = r = 1 ; q[1] = 0 ;
for ( int i = 1 ; i <= tp ; i ++ ) {
while ( l<r && slop(q[l],q[l+1])<double(b[i].x) ) l ++ ;
int j = q[l] ; f[i] = f[j] + b[j+1].y*b[i].x ;
while ( l<r && slop(q[r-1],q[r])>slop(q[r],i) ) r -- ;
q[++r] = i ;
}
printf ( "%lld\n" , f[tp] ) ; return 0 ;
}
题目描述
【题目描述】
有n个数,分成连续的若干段,每段的分数为ax^2+bx+c(a,b,c是给出的常数),其中x为该段的各个数的和。求如何分才能使得各个段的分数的总和最大。
【输入格式】
第1行:1个整数N (1 <= N <= 1000000)。
第2行:3个整数a,b,c(-5<=a<=-1,|b|<=10000000,|c|<=10000000
下来N个整数,每个数的范围为[1,100]。
【输出格式】
一个整数,各段分数总和的值最大。
SAMPLE INPUT
4
-1 10 -20
2 2 3 4
SAMPLE OUTPUT
9
这是caioj斜率优化专题里面最难的一道题,虽然和老师做法不一样,但我还是AC了
既然前面已经讲了这么多题了,现在就直接上公式了
f[i] = f[j] + a*(s[i]-s[j])*(s[i]-s[j])+b*(s[i]-s[j])+c
f[i] = f[j] + a*(s[i]*s[i]-2*s[i]*s[j]+s[j]*s[j]) + b*s[i] - b*s[j] + c
f[i] = f[j] + a*s[i]*s[i] - 2a*s[i]*s[j] + a*s[j]*s[j] + b*s[i] - b*s[j] + c
f[i]-a*s[i]*s[i]-c-b*s[i] + 2a*s[i]*s[j] = f[j] + a*s[j]*s[j] - b*s[j]
f[j] + a*s[j]*s[j] - b*s[j] = 2a*s[i]*s[j] + f[i]-a*s[i]*s[i]-c-b*s[i]
y = f[j]+a*s[j]*s[j]-b*s[j] ( 单调递减 )
k = 2a*s[i] ( 单调递减)
x = s[j] ( 单调递增)
b = f[i]-a*s[i]*s[i]-c-b*s[i]
可以看出这道题维护的是上凸壳,上凸壳维护的原理我就不细讲了,大家可以借助斜率优化详解(一)中间的方法推,其实还是很容易的
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cstdlib>
using namespace std ;
typedef long long LL ;
const int N = 1e6 + 10 ;
int n ; LL s[N] ;
LL a , b , c ;
int l , r , q[N] ;
LL f[N] ;
LL X ( int j ) {
return s[j] ;
}
LL Y ( int j ) {
return f[j]+a*s[j]*s[j]-b*s[j] ;
}
double slop ( int i , int j ) {
return double(Y(j)-Y(i))/double(X(j)-X(i)) ;
}
int main() {
scanf ( "%d" , &n ) ; LL x ;
scanf ( "%lld%lld%lld" , &a , &b , &c ) ;
for ( int i = 1 ; i <= n ; i ++ ) {
scanf ( "%lld" , &x ) ;
s[i] = s[i-1] + x ;
}
l = r = 1 ; q[1] = 0 ;
for ( int i = 1 ; i <= n ; i ++ ) {
while ( l<r && slop(q[l],q[l+1])>double(2*a*s[i]) ) l ++ ;
int j = q[l] ; f[i] = f[j]+a*(s[i]-s[j])*(s[i]-s[j])+b*(s[i]-s[j])+c ;
while ( l<r && slop(q[r-1],q[r])<slop(q[r],i) ) r -- ;
q[++r] = i ;
}
printf ( "%lld\n" , f[n] ) ; return 0 ;
}
/*
f[i] = f[j] + a*(s[i]-s[j])*(s[i]-s[j])+b*(s[i]-s[j])+c
f[i] = f[j] + a*(s[i]*s[i]-2*s[i]*s[j]+s[j]*s[j]) + b*s[i] - b*s[j] + c
f[i] = f[j] + a*s[i]*s[i] - 2a*s[i]*s[j] + a*s[j]*s[j] + b*s[i] - b*s[j] + c
f[i]-a*s[i]*s[i]-c-b*s[i] + 2a*s[i]*s[j] = f[j] + a*s[j]*s[j] - b*s[j]
f[j] + a*s[j]*s[j] - b*s[j] = 2a*s[i]*s[j] + f[i]-a*s[i]*s[i]-c-b*s[i]
y = f[j]+a*s[j]*s[j]-b*s[j] ( 单调递减 )
k = 2a*s[i] ( 单调递减)
x = s[j] ( 单调递增)
b = f[i]-a*s[i]*s[i]-c-b*s[i]
*/