有 n n n 根柱子,每根柱子有高度 h i h_i hi,在柱子 i i i 和柱子 j j j 之间建桥的代价为 ( h i − h j ) 2 (h_i-h_j)^2 (hi−hj)2。每根没建桥的柱子需要花费 w i w_i wi 的代价拆除。询问使第 1 1 1 根柱子和第 n n n 根柱子连接的最小代价。桥不能在柱子之外的地方相交。
令 f i f_i fi 为使 1 1 1 和 i i i 连接的最小代价。枚举最后一座桥的起点 j j j, f i = min j < i { f j + ∑ k = j + 1 i − 1 w k + ( h i − h j ) 2 } f_i = \min\limits_{jfi=j<imin{fj+k=j+1∑i−1wk+(hi−hj)2}
前缀和优化一下: f i = min j < i { f j + s i − 1 − s j + ( h i − h j ) 2 } f_i = \min\limits_{jfi=j<imin{fj+si−1−sj+(hi−hj)2}
有 h i × h j h_i\times h_j hi×hj项,可以考虑斜率优化。
整理式子: f j + h j 2 − s j = 2 h i × h j + f i − h i 2 − s i − 1 f_j+h_j^2-s_j = 2h_i\times h_j+f_i-h_i^2-s_{i-1} fj+hj2−sj=2hi×hj+fi−hi2−si−1
y = f j + h j 2 − s j , k = 2 h i , x = h j , b = f i − h i 2 − s i − 1 y = f_j+h_j^2-s_j, k = 2h_i, x = h_j, b = f_i-h_i^2-s_{i-1} y=fj+hj2−sj,k=2hi,x=hj,b=fi−hi2−si−1
容易发现 k k k 和 x x x 都不单调。 x x x 的单调性影响维护凸壳的方式, k k k 的单调性影响寻找最优决策点的方式
可以通过离线的方式解决:cdq分治!(不会平衡树维护凸包)
cdq分治中需要计算左侧对右侧的贡献。
如果左侧 x x x 递增,则可以用单调队列维护左侧的凸壳;如果右侧斜率 k k k 递增,则具有决策单调性。
对右侧点来说,决策点要么位于右部,要么位于左部(怎么感觉有点废话),所以cdq分治不会遗漏决策点。
时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)
#include
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e5+10;
const int maxm = 1e5+10;
const double eps = 1e-10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int, int> pii;
int n;
ll h[maxn], s[maxn], w[maxn], dp[maxn];
int q[maxn], hh, tt;
struct node{
ll k, x, y, id;
bool operator < (const node &b) const{
if(x != b.x)
return x < b.x;
else
return y < b.y;
}
}a[maxn], tmp[maxn];
bool cmp(node a, node b){
return a.k < b.k;
}
long double slope(int i, int j){
long double x1 = (long double)a[i].x;
long double y1 = (long double)a[i].y;
long double x2 = (long double)a[j].x;
long double y2 = (long double)a[j].y;
if(fabs(x2-x1) < eps)
return y2 > y1 ? 1e18 : -1e18;
else
return (y2-y1)/(x2-x1);
}
void cdq(int l, int r){
if(l == r){
int j = a[l].id;
a[l].y = dp[j] + h[j]*h[j] - s[j];
return;
}
int mid = (l+r) >> 1;
int p1 = l, p2 = mid+1;
for(int i = l; i <= r; i++){
if(a[i].id <= mid)
tmp[p1++] = a[i];
else
tmp[p2++] = a[i];
}
for(int i = l; i <= r; i++)
a[i] = tmp[i];
cdq(l, mid);
hh = 1, tt = 0;
for(int i = l; i <= mid; i++){
while(hh < tt && slope(q[tt-1], q[tt]) >= slope(q[tt], i))
tt--;
q[++tt] = i;
}
for(int i = mid+1; i <= r; i++){
while(hh < tt && slope(q[hh], q[hh+1]) <= a[i].k)
hh++;
if(hh <= tt){
int j = q[hh];
int id = a[i].id;
dp[id] = min(dp[id], a[j].y - a[i].k*a[j].x + s[id-1]+ h[id]*h[id]);
}
}
cdq(mid+1, r);
p1 = l, p2 = mid+1;
int p = l;
while(p1 <= mid && p2 <= r){
if(a[p1] < a[p2])
tmp[p++] = a[p1++];
else
tmp[p++] = a[p2++];
}
while(p1 <= mid)
tmp[p++] = a[p1++];
while(p2 <= r)
tmp[p++] = a[p2++];
for(int i = l; i <= r; i++)
a[i] = tmp[i];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++)
cin >> h[i];
for(int i = 1; i <= n; i++){
cin >> w[i];
s[i] = s[i-1] + w[i];
}
for(int i = 1; i <= n; i++){
a[i].k = 2ll * h[i];
a[i].x = h[i];
a[i].id = i;
}
memset(dp, INF, sizeof(dp));
dp[1] = 0;
sort(a+1, a+1+n, cmp);
cdq(1, n);
cout << dp[n] << endl;
return 0;
}