柱状图 [三分法+树状数组]
题目描述
(WTH)获得了一个柱状图,这个柱状图一共有(N)个柱子,最开始第(i)根柱子的高度为(x_i),他现在要将这个柱状图排成一个屋顶的形状,屋顶的定义如下:
(1.)屋顶存在一个最高的柱子,假设为(i),最终高度为(h_i)。它是所有柱子之中最高的。
(2.)第(j)根柱子的高度为(h_j=h_i-|i-j|),但这个高度必须大于(0),否则就是不合法的。
(WTH)可以对一个柱子做的操作只有将其高度加一或减一,(WTH)正忙着享受自己的人赢生活于是他将把这个柱状图变成屋顶的任务交给了你。你需要求出最少进行多少次操作才能够把这个柱状图变成一个屋顶形状。
输入格式
第一行包含一个正整数(N)。
第二行包含(N)个用空格隔开的正整数,表示(x_i),含义如题面。
输出格式
输出最少进行多少个操作才能够把这个柱状图变成屋顶的形状。
样例
样例输入:
4
1 1 2 3
样例输出:
3
数据范围与提示
样例解释:
例一升高(2,3,4)号柱子一个单位高度是操作最少的方法之一,最高处为第四个柱子。例二降低第三根柱子三个高度,升高第四个柱子一个高度。最高处为第(2)个柱子。
数据范围:
(30\%)的数据满足:(1leqslant Nleqslant 100)。
(60\%)的数据满足:(1leqslant Nleqslant 5,000)。
(100\%)的数据满足:(1leqslant Nleqslant 100,000),(1leqslant h_ileqslant {10}^9)。
分析
这道题暴力解法有很多,正解也非常的巧妙和细节。
1、30分解法
这个题的不确定性就在于选取哪一个当作高度的最大和高度最大为多少,那么拿(30)分的话我们就可以直接枚举作为最高的那个点,并且枚举高度,找到一个最小值,那么就有(30)分了,时间复杂度(Theta(n^2 max(H_i)))。
30分代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100000+3;
ll ans;
int n,a[maxn],minn;
int main(){
scanf("%d",&n);
minn=0x3f3f3f3f;
ans=9223372036854775807;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
minn=min(minn,a[i]);
}
for(int i=1;i<=n;i++){
for(int h=max(minn,max(i,n-i+1));h;h++){
ll sum=0;
bool flag=1;
for(int j=1;j<=n;j++) if(h<a[i]) flag=0;
for(int j=1;j<=n;j++){
sum+=abs(a[j]-h+abs(i-j));
if(sum>ans) break;
}
ans=min(ans,sum);
if(flag) break;
}
}
printf("%lld
",ans);
return 0;
}
2、60分解法
总的思路和刚刚的(30)分解法差不多,但是需要一些优化。
因为这个题中最高的高度过大或者过小,都会导致最终的答案不是最优的,那么我们可以得到这个题答案的一个变化曲线应该是一个(U)型,有一个最低点,那么我们就可以先枚举把哪个点作为最高,然后三分最低时候的高度,这样的时间复杂度是(Theta(n^2 logn))
60分代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10;
int n,a[N],b[N];
int calc(int pos,int h){
for(int i=1;i<=n;++i)
b[i]=h-abs(i-pos);
int res=0;
for(int i=1;i<=n;++i){
res+=abs(a[i]-b[i]);
}
return res;
}
int ans=0x3f3f3f3f3f3f3f3f;
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;++i)
scanf("%lld",&a[i]);
for(int i=1;i<=n;++i){
int l=max(i,n-i+1),r=1e9;
while(l <= r){
int block=(r-l)/3;
int m1=l+block,m2=r-block;
if(calc(i,m1) < calc(i,m2)) r=m2-1;
else l=m1+1;
}
ans=min(ans,calc(i,r));
}
printf("%lld
",ans);
return 0;
}
100分解法
正解我们可以从上边的(60)分解法中找灵感,
在(60)分的做法中,我们在求函数值时会有一个(Theta(n))的时间开销
现在我们考虑怎么将其优化
我们设当前最高点的坐标为(x),改造后其高度为(h[x]),第i根柱子改造前的高度为(a[i]),改造后的高度为(h[i])
对于某一根柱子(i),要想形成屋顶的形状,那么必须满足(|h[x]−h[i]|=|x−i|)
我们来分情况讨论:
一、如果该柱子的编号小于(x),那么应满足(h[x]−h[i]=x−i)
我们移一下项,得到改造后的高度为(h[i]=h[x]−x+i)
如果(h[i]geqslant a[i]),由于绝对值的原因,分为两种情况:
(1、),(h[i] geqslant a[i]),那么变化的高度为(h[x]−x+i−a[i])
(2、)如果(h[i]<a[i]),变化的高度为(a[i]−h[x]+x−i)
二、如果该柱子的编号大于x,那么应满足(h[x]+x=h[i]+i)
则改造后的高度为(h[i]=h[x]+x−i)
(1、)如果(h[i]geqslant a[i]),变化的高度为(h[x]+x−i−a[i])
(2、)如果(h[i]<a[i]),变化的高度为(a[i]−h[x]−x+i)
那么最终的价值就是这四种情况的总和,分别为:
我们发现这就是一个前缀和
因此我们想到用树状数组去优化
(cnt_1,cnt_2,cnt_3,cnt_4)的求解我们可以用二分去解决
100分代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 1e5+10;
int lowbit(int x){
return x & -x;
}
int n,h[maxn],rkl[maxn],rkr[maxn];
struct Node{
int pos,val;
Node(){}
Node(int x,int y){
pos = x;
val = y;
}
bool operator <(const Node& A)const{
return val < A.val;
}
}jll[maxn],jlr[maxn];
struct T{
int sum[maxn],cnt[maxn];
}leftre,rigtre;
struct queryt{
int x,y;
queryt(){
x = 0;
y = 0;
}
};
void modifyL(int x,int val,int num){//左边单点修改
while(x <= n){
leftre.sum[x] += val;
leftre.cnt[x] += num;
x += lowbit(x);
}
}
queryt queryleft(int x){//左边单点查询
queryt ans;
while(x){
ans.x += leftre.sum[x];
ans.y += leftre.cnt[x];
x -= lowbit(x);
}
return ans;
}
void modifyR(int x,int val,int num){//右边单点修改
while(x <= n){
rigtre.sum[x] += val;
rigtre.cnt[x] += num;
x += lowbit(x);
}
}
queryt queryjL(int l,int r){//左边区间查询
queryt Le = queryleft(l-1);
queryt Ri = queryleft(r);
queryt ans;
ans.x = Ri.x - Le.x;
ans.y = Ri.y - Le.y;
return ans;
}
queryt queryright(int x){//右边单点查询
queryt ans;
while(x){
ans.x += rigtre.sum[x];
ans.y += rigtre.cnt[x];
x -= lowbit(x);
}
return ans;
}
queryt queryjR(int l,int r){//右边区间查询
queryt Le = queryright(l-1);
queryt Ri = queryright(r);
queryt ans;
ans.x = Ri.x - Le.x;
ans.y = Ri.y - Le.y;
return ans;
}
int erfen(Node a[],int val){
int l = 1,r = n;
while(l <= r){
int mid = l + r >> 1;
if(a[mid].val <= val)l = mid + 1;
else r = mid - 1;
}
return r;
}
int cale(int now,int gd){
int ans = abs(h[now] - gd);
queryt hf;//这里是花费
int pos = erfen(jll,gd-now);
hf=queryleft(pos);
ans += (gd-now)*hf.y - hf.x;
hf = queryjL(pos+1,n);
ans += hf.x - (gd-now)*hf.y;
pos = erfen(jlr,gd+now);
hf = queryright(pos);
ans += (gd+now)*hf.y - hf.x;
hf = queryjR(pos+1,n);
ans += hf.x - (now + gd) * hf.y;
return ans;
}
int Mx = 0x3f3f3f3f3f3f3f3f;
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;++i){
scanf("%lld",&h[i]);
}
for(int i=1;i<=n;++i){
jll[i].pos = i;
jll[i].val = h[i]-i;
jlr[i].pos = i;
jlr[i].val = h[i]+i;
}
sort(jll+1,jll+1+n);
sort(jlr+1,jlr+1+n);
for(int i=1;i<=n;++i){
rkr[jlr[i].pos] = i;
rkl[jll[i].pos] = i;
}
for(int i=1;i<=n;++i){
modifyR(rkr[i],jlr[rkr[i]].val,1);
}
for(int i=1;i<=n;++i){
modifyR(rkr[i],-jlr[rkr[i]].val,-1);
int nl = max(i,(n-i+1)),nr = 1e9,lmid,rmid,mid;
while(nl+1<nr){
mid = nl + nr >> 1;
lmid = mid-1;
rmid = mid;
int hfl = cale(i,lmid);
int hfr = cale(i,rmid);
if(hfl >= hfr)nl = mid;
else nr = mid;
}
Mx = min(Mx,cale(i,nl));
Mx = min(Mx,cale(i,nr));
modifyL(rkl[i],jll[rkl[i]].val,1);
}
printf("%lld
",Mx);
return 0;
}