bzoj 2149 拆迁队 斜率优化+cdq分治

题面

题目传送门

解法

从来没写过这样的……

  • 第一问非常简单,能够从 j j j转移到 i i i的条件显然为 a [ i ] − a [ j ] ≥ i − j a[i]-a[j]≥i-j a[i]a[j]ij,移项可得 a [ i ] − i ≥ a [ j ] − j a[i]-i≥a[j]-j a[i]ia[j]j。不妨令 x [ i ] = a [ i ] − i x[i]=a[i]-i x[i]=a[i]i,那么在 O ( n log ⁡ n ) O(n\log n) O(nlogn)的复杂度内做最长不下降子序列就可以了。
  • 对于第二问,假设 g [ i ] g[i] g[i]为保留第 i i i个房子的最小花费,那么转移比较显然: g [ i ] = m i n { g [ j ] + ( 2 ∗ a [ j ] + i − j ) ( i − j − 1 ) 2 } + a [ i ] + b [ i ] g[i]=min\{g[j]+\frac{(2*a[j]+i-j)(i-j-1)}{2}\}+a[i]+b[i] g[i]=min{g[j]+2(2a[j]+ij)(ij1)}+a[i]+b[i]。把式子稍作化简一下可以得到 g [ i ] = m i n { g [ j ] − ( j + 1 ) a [ j ] + j ( j + 1 ) 2 + i ∗ x [ j ] } + a [ i ] + b [ i ] + i ( i − 1 ) 2 g[i]=min\{g[j]-(j+1)a[j]+\frac{j(j+1)}{2}+i*x[j]\}+a[i]+b[i]+\frac{i(i-1)}{2} g[i]=min{g[j](j+1)a[j]+2j(j+1)+ix[j]}+a[i]+b[i]+2i(i1)
  • 当然,能够从 j j j转移到 i i i的条件有三个:1. f [ j ] + 1 = f [ i ] f[j]+1=f[i] f[j]+1=f[i]; 2. x [ j ] ≤ x [ i ] x[j]≤x[i] x[j]x[i]; 3. j &lt; i j&lt;i j<i
  • 简便起见,我们令 y [ i ] = g [ i ] − ( j + 1 ) a [ i ] + i ( i + 1 ) 2 y[i]=g[i]-(j+1)a[i]+\frac{i(i+1)}{2} y[i]=g[i](j+1)a[i]+2i(i+1)
  • 假设现在有两个决策点 j , k j,k j,k,并且 j j j更优,那么可以得到 y [ j ] + i ∗ x [ j ] &lt; y [ k ] + i ∗ x [ k ] y[j]+i*x[j]&lt;y[k]+i*x[k] y[j]+ix[j]<y[k]+ix[k],移项就可以变成 y [ j ] − y [ k ] &lt; i ( x [ k ] − x [ j ] ) y[j]-y[k]&lt;i(x[k]-x[j]) y[j]y[k]<i(x[k]x[j])。因为不知道 x [ j ] , x [ k ] x[j],x[k] x[j],x[k]的大小关系,我们不妨强制认为 x [ k ] &lt; x [ j ] x[k]&lt;x[j] x[k]<x[j],那么就可以得到 y [ j ] − y [ k ] x [ k ] − x [ j ] &gt; i \frac{y[j]-y[k]}{x[k]-x[j]}&gt;i x[k]x[j]y[j]y[k]>i的关系。
  • 然后发现关系就变成了一个类似于斜率的式子,可以考虑斜率优化。
  • 可以将每一个决策点用 ( − x [ i ] , y [ i ] ) (-x[i],y[i]) (x[i],y[i])的方式扔进坐标系内,显然,我们要得到的是一个下凸壳,那么类似于NOI2007货币兑换的做法,可以用cdq分治来维护这样一个下凸壳。
  • 然后考虑如何满足下标的几个限制。首先,我们可以分层进行dp,就是用 f [ i ] = t f[i]=t f[i]=t的点来更新 f [ i ] = t + 1 f[i]=t+1 f[i]=t+1的点。对于 x [ j ] ≤ x [ i ] x[j]≤x[i] x[j]x[i] j &lt; i j&lt;i j<i的限制在cdq分治中即可实现。
  • 在查询的时候直接在下凸壳上二分就可以了。
  • 时间复杂度: O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x > y ? y : x;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 100010; const ll inf = 1ll << 60;
int n, top, a[N], b[N], f[N], st[N]; ll g[N], x[N], y[N];
vector <int> v[N];
struct Node {
	int fl, id;
	bool operator < (const Node &q) const {return id < q.id;}
} q[N * 2];
bool cmp(int i, int j) {return (x[i] == x[j]) ? y[i] > y[j] : x[i] < x[j];}
double slope(int i, int j) {return 1.0 * (y[i] - y[j]) / (x[j] - x[i]);}
int calcf() {
	int len = 0;
	for (int i = 1; i <= n; i++) {
		if (x[i] < 0) {f[i] = n + 1; continue;}
		int l = 1, r = len, ans = 0;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (st[mid] <= x[i]) ans = mid, l = mid + 1;
				else r = mid - 1;
		}
		f[i] = ans + 1;
		if (ans == len) st[++len] = x[i]; else st[ans + 1] = x[i];
	}
	return len;
}
void query(int i) {
	if (!top) return;
	int l = 1, r = top - 1, j = st[top];
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (slope(st[mid], st[mid + 1]) < i) j = st[mid], r = mid - 1;
			else l = mid + 1;
	}
	chkmin(g[i], g[j] + 1ll * (2 * a[j] + i - j) * (i - j - 1) / 2 + a[i] + b[i]);
}
void solve(int l, int r) {
	if (l == r) return;
	static int l1, l2, L[N], R[N];
	int mid = (l + r) >> 1;
	solve(l, mid), solve(mid + 1, r); top = l1 = l2 = 0;
	for (int i = l; i <= mid; i++) if (!q[i].fl) L[++l1] = q[i].id;
	for (int i = mid + 1; i <= r; i++) if (q[i].fl) R[++l2] = q[i].id;
	if (!l1 || !l2) return;
	sort(L + 1, L + l1 + 1, cmp), sort(R + 1, R + l2 + 1, cmp);
	for (int i = 1, j = 1; i <= l2; i++) {
		while (j <= l1 && x[L[j]] <= x[R[i]]) {
			if (top && x[st[top]] == x[L[j]]) top--;
			while (top >= 2 && slope(st[top - 1], st[top]) > slope(st[top], L[j])) top--;
			st[++top] = L[j++];
		}
		query(R[i]);
	}
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++) read(a[i]), x[i] = a[i] - i;
	for (int i = 1; i <= n; i++) read(b[i]), g[i] = inf;
	int tx = calcf(); cout << tx << ' ';
	for (int i = 0; i <= n; i++) v[f[i]].push_back(i);
	for (int t = 0; t < tx; t++) {
		int len = 0;
		for (int i = 0; i < v[t].size(); i++) q[++len] = (Node) {0, v[t][i]};
		for (int i = 0; i < v[t + 1].size(); i++) q[++len] = (Node) {1, v[t + 1][i]};
		sort(q + 1, q + len + 1);
		solve(1, len);
		for (int i = 0; i < v[t + 1].size(); i++) {
			int j = v[t + 1][i];
			y[j] = -1ll * (j + 1) * a[j] + g[j] + 1ll * j * (j + 1) / 2;
		}
	}
	ll ans = inf;
	for (int i = 1; i <= n; i++)
		if (f[i] == tx) chkmin(ans, g[i] + 1ll * (2 * a[i] + n - i + 1) * (n - i) / 2);
	cout << ans << "\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值