柱状图 (树状数组 三分 二分)

题目链接

题目

WTH获得了一个柱状图,这个柱状图一共有N个柱子,最开始第i根柱子的高度为xi,他现在要将这个柱状图排成一个屋顶的形状,屋顶的定义如下:

  1. 屋顶存在一个最高的柱子,假设为i, 最终高度为hi.它是所有柱子之中最高的.

  2. 第j根柱子的高度为hj=hi-|i-j|, 但这个高度必须大于0, 否则就是不合法的.

WTH可以对一个柱子做的操作只有将其高度加一或减一, WTH正忙着享受自己的人赢生活于是他将把这个柱状图变成屋顶的任务交给了你.你需要求出最少进行多少次操作才能够把这个柱状图变成一个屋顶形状。

分析

  1. 首先我们需要枚举每一个点做最高点时的状况。
  2. 其次我们需要求出每一个点做最高点时高度取多少时代价最小。
  3. 然后感性理解一下可以发现随着高度的变化它是一个单峰函数。
  4. 我们可以三分出最优高度。
  5. 按照朴素算法我们可以从 (1-n) 计算结果,复杂度 (O(n^2logn))
  6. 然后可以想到优化求结果的过程。
  7. 后面高能预警:
    考虑朴素求法, 我们需要拆掉两个绝对值,可以分四种情况 :
    设最高点为 (mid), 最高值为 (H), 当前点为 (i), 当前点的初始值为 (a[i])
    第一种(mid - i > 0), $(H - (mid - i)) - a[i] > 0 $
    第二种(i - mid > 0), $a[i] - (H - (i - mid)) > 0 $
    第三种(i - mid > 0), $(H - (i - mid)) - a[i] > 0 $
    第四种(mid - i > 0), $a[i] - (H - (mid - i)) > 0 $
  8. 然后我们会发现一直有 (a[i] - i)(a[i] + i) 这几个值。
  9. 我们可以考虑用树状数组优化一下。
  10. 维护两个树状数组,可以进行区间查询。
  11. 总时间复杂度 (O(nlog^2n))
    然后分析完了,建议大家仔细理解四个式子的含义。

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e5 + 5;
int n, a[N], rkl[N], rkr[N], trl[N], trr[N], ctl[N], ctr[N], ans = 0x3f3f3f3f3f3f3f3f;
pair<int ,int> pl[N], pr[N];
inline int lowbit(int x) { return x & -x; }
inline void addl(int x, int w, int c) {
	while(x <= n) trl[x] += w, ctl[x] += c, x += lowbit(x);
}
inline void addr(int x, int w, int c) {
	while(x <= n) trr[x] += w, ctr[x] += c, x += lowbit(x);
}
pair<int ,int> suml(int x) {//前缀和询问
	int tmp = 0, res = 0;
	while(x) {
		tmp += trl[x], res += ctl[x], x -= lowbit(x);
	}
	return make_pair(tmp, res);
}
pair<int, int> sumr(int x) {
	int tmp = 0, res = 0;
	while(x) {
		tmp += trr[x], res += ctr[x], x -= lowbit(x);
	}
	return make_pair(tmp, res);
}
inline pair<int, int> askl(int l, int r) {//区间询问
	if(l > r) return make_pair(0LL, 0);
	pair<int, int> L = suml(l - 1), R = suml(r);
	return make_pair(R.first - L.first, R.second - L.second); 
}
inline pair<int, int> askr(int l, int r) {
	if(l > r) return make_pair(0LL, 0);
	pair<int, int> L = sumr(l - 1), R = sumr(r);
	return make_pair(R.first - L.first, R.second - L.second);
}
int erfen(pair<int, int> p[], int w) {//二分查找小于等于变号那个位置的最大值。
	int l = 1, r = n, res = n+1;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(p[mid].first > w) r = mid - 1;
		else l = mid + 1, res = mid;
	}
	return res;
}
inline int suan(int x, int h) {
	int ans = abs(a[x] - h), pos;
	pair<int, int> tmp;
	pos = erfen(pl, h - x);
	tmp = suml(pos);//第一种情况
	ans += 1LL * (h - x) * tmp.second - tmp.first;
	tmp = askl(pos + 1, n);//第二种情况
	ans -= 1LL * (h - x) * tmp.second - tmp.first;
	pos = erfen(pr, h + x);
	tmp = sumr(pos);//第三种
	ans += 1LL * (h + x) * tmp.second - tmp.first;
	tmp = askr(pos + 1, n);//第四种
	ans -= 1LL * (h + x) * tmp.second - tmp.first;
	return ans;
}
signed main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	cin >> n;
	for(register int i = 1; i <= n; ++i) 
		cin >> a[i], pl[i] = make_pair(a[i] - i, i), pr[i] = make_pair(a[i] + i, i);
	sort(pl + 1, pl + n + 1), sort(pr + 1, pr + n + 1); // 排序
	for(register int i = 1; i <= n; ++i) 
		rkl[pl[i].second] = i, rkr[pr[i].second] = i; // 排名原始位置
	for(register int i = 1; i <= n; ++i)
		addr(rkr[i], a[i] + i, 1); //加入进去
	for(register int i = 1; i <= n; ++i) {
		addr(rkr[i], -a[i] - i, -1); // 先把它自己删掉
		int l = max(i, n - i + 1), r = 1e9;//三分
		while(r - l > 1) {
			int mid = (l + r) >> 1;
			int lx = suan(i, mid - 1), lr = suan(i, mid);
			if(lx < lr) r = mid;
			else l = mid;
		}
		ans = min(ans, min(suan(i, l), suan(i, r)));
		addl(rkl[i], a[i]-i, 1);//它已经变成了下一个的左边,把它加入进去。
	}
	return cout << ans, 0;
}

原文地址:https://www.cnblogs.com/DZN2004/p/13442672.html