Processing math: 0%

『柱状图 三分法求极值 树状数组』

Parsnip·2019-04-09 21:48·362 次阅读

『柱状图 三分法求极值 树状数组』

<更新提示>

<第一次更新>


<正文>

柱状图#

Description#

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

  1. 屋顶存在一个最高的柱子,假设为i,最终高度为hi.它是所有柱子之中最 高的.
  2. 第j根柱子的高度为hj=hi-|i-j|,但这个高度必须大于0,否则就是不合法的.

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

Input Format#

第一行包含一个正整数 N(1 ≤ N≤ 100 000).
第二行包含 N 个用空格隔开的正整数,表示 xi,含义如题面。

Output Format#

输出最少进行多少个操作才能够把这个柱状图变成屋顶的形状。

Sample Input#

Copy
5 4 5 7 2 2

Sample Output#

Copy
4

解析#

先考虑一下暴力思路,首先,枚举最高柱子是哪个肯定是少不了的,这个需要一重循环来枚举。对于一个最高点,如果我们知道了这个最高点的高度,我们就能依次确定其他柱子的高度,从而确定代价,就能得到最小花费。

如果直接暴力枚举最高点的高度的话,时间复杂度就是O(n2max,可以得30分。

考虑一下这个函数模型的数学性质:设f(x)代表最高柱子高度为x时的调整所需花费。显然,当x取到一个恰当的值的时候,可以使得花费最小,当然,这样的值可能有很多个。但是不难发现只要任何一个其他的x'不能取得更优的值,就可以保证这是一个单谷函数(由于相邻的柱子高度差只能为1)。

那么就可以直接三分法枚举高度,暴力统计代价,时间复杂度O(n^2log_2n),可以得到60分。

其实这就是正解的思路,不难发现这个算法枚举和三分是少不了的,所以瓶颈就在统计花费上,如果能够快速统计花费,就可以解决本题。

注意到如下两个性质:当最高点为i,高度为h_i时,经过调整后,\forall j<i╞\ h_i-i=h_j-j,\forall j>i╞\ h_i+i=h_j+j

那么利用这两个性质,我们可以把这两个关键值存起来并排序。对于一个最高点为x,高度为h_x,我们利用二分查找找到关键值<=h_x-x的元素在如上数组中的最大下标为pos,那么最高点左边调整花费可以表示为

\left [ cnt_l*(h_x-x)-\sum_{i=1}^{pos}(h_i-i) \right ] + \left [ \sum_{i=pos+1}^{n}(h_i-i)-cnt_r*(h_x-x) \right ]$$,$cnt_l$代表关键值数组中关键值$<=h_x-x$且原下标小于$x$的元素的数量(**也就是说,下标不满足条件的元素我们是忽视的,$\sum$中也不参与计算**),$cnt_r$代表关键值数组中关键值$>h_x-x$且原下标小于$x$的元素的数量。 不难发现,$cnt$和$\sum$都是可以表示为前缀和(部分和)结构的($cnt$即为每个符合要求的相同元素的出现次数的前缀和),而对于新的最高点$x+1$的枚举,我们需要将相对原下标小于$x+1$的元素加入统计的范畴中,这样,涉及到插入和前缀和查询操作,我们可以用树状数组维护。 同理,对于最高点右边的调整花费,我们二分关键值$<=h_x+x$的元素在关键值数组中的最大下标$pos$,也用树状数组维护并统计即可。其计算式为$$\left [ cnt_l*(h_x+x)-\sum_{i=1}^{pos}(h_i+i) \right ] + \left [ \sum_{i=pos+1}^{n}(h_i+i)-cnt_r*(h_x+x) \right ]

当然,调整最高点的花费也是需要累加的。

总的来说,我们在三分求解时利用两个树状数组来维护最高点两边的关键值,然后利用树状数组的求前缀和功能计算花费即可。

Code:

Copy
#include<bits/stdc++.h> using namespace std; #define mset(name,val) memset(name,val,sizeof name) #define lowbit(x) ( (x) & (-x) ) #define Make(a,b) make_pair(a,b) const int N=100070,INF=1e9; long long n,h[N],rankl[N],rankr[N]; long long ans; //pair - inline pair<long long,long long> reduce(pair<long long,long long> a,pair<long long,long long> b) { return Make(a.first-b.first,a.second-b.second); } //一个点关键值及原下标 struct node { long long val,pos; bool operator < (const node &t)const { return val < t.val; } }; //树状数组 struct Binary_Indexed_Tree { //两个值:元素出现次数的前缀和 元素值的前缀和 long long s[N],cnt[N]; //插入一个值 inline void insert(long long pos,long long val,long long num) { for (;pos<=n;pos+=lowbit(pos)) s[pos]+=val,cnt[pos]+=num; } //单点前缀和查询 inline pair<long long,long long> query(long long pos) { pair<long long,long long> res=Make(0LL,0LL); for (;pos>=1;pos-=lowbit(pos)) res.first+=s[pos],res.second+=cnt[pos]; return res; } //区间部分和查询 inline pair<long long,long long> secquery(long long l,long long r) { if (l>r)return Make(0LL,0LL); else return reduce( query(r) , query(l-1) ); } }; node l[N],r[N]; Binary_Indexed_Tree Left,Right; //二分查找一个节点数组中关键值<=val元素的最大下标 inline long long find(node *p,long long val) { long long l=1,r=n; while ( l+1 < r ) { long long mid=(l+r)/2; if (p[mid].val>val)r=mid; else l=mid; } if (p[r].val<=val)return r; else if (p[l].val<=val)return l; else return 0; } //计算当第x根柱子作为最高柱且高度为height时,调整柱子高度所需的花费 inline long long calc(long long x,long long height) { //调整最高柱子的初始花费 long long res=abs(h[x]-height); //其他柱子关键值的前缀和查询,关键值临界点下标 pair<long long,long long> cost;long long pos; //计算最高柱子左边的花费 //找到小于等于关键值的最大下标 pos=find(l,height-x); //查询前缀和 cost=Left.query(pos); //计算花费 res += (height-x)*cost.second*1LL - cost.first; //查询右边的部分和 cost=Left.secquery(pos+1,n); //计算花费 res += cost.first - (height-x)*cost.second*1LL; //同理,计算最高柱子右边的花费 pos=find(r,height+x); cost=Right.query(pos); res += (height+x)*cost.second*1LL - cost.first; cost=Right.secquery(pos+1,n); res += cost.first - (height+x)*cost.second*1LL; return res; } inline void input(void) { scanf("%lld",&n); for (int i=1;i<=n;i++) scanf("%lld",&h[i]); } inline void init(void) { //建立l,r两个储存关键值的数组 for (int i=1;i<=n;i++) l[i]=(node){h[i]-i,i},r[i]=(node){h[i]+i,i}; //按照关键值排序 sort(l+1,l+n+1); sort(r+1,r+n+1); ans=LONG_LONG_MAX; //键入原下标,得到排序后的排名(排序后的新下标) for (int i=1;i<=n;i++) rankl[ l[i].pos ] = i , rankr[ r[i].pos ] = i; } inline void solve(void) { //先将所有柱子加入右树状数组中 for (int i=1;i<=n;i++) Right.insert( rankr[i] , r[ rankr[i] ].val , 1 ); //枚举最高柱子 for (int i=1;i<=n;i++) { //先将自己删除 Right.insert( rankr[i] , -r[ rankr[i] ].val , -1 ); //三分高度 long long lbound=max(i*1LL,(n-i+1)*1LL),ubound=INF; while ( lbound+1 < ubound ) { long long mid=(lbound+ubound)/2; long long lmid=mid-1,rmid=mid; long long lcost=calc(i,lmid),rcost=calc(i,rmid); //找单谷函数极小值点 if (lcost>=rcost)lbound=mid; else ubound=mid; } //避免误差,左右边界相邻并退出循环时,利用左右端点分别更新一次答案 ans = min(ans,calc(i,lbound)); ans = min(ans,calc(i,ubound)); //将这个处理好的点压入左边的树状数组中 Left.insert( rankl[i] , l[ rankl[i] ].val , 1 ); } } int main(void) { freopen("column.in","r",stdin); freopen("column.out","w",stdout); input(); init(); solve(); printf("%lld\n",ans); return 0; }

<后记>

posted @   Parsnip  阅读(362)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
目录