[CSP-S模拟测试]:柱状图(树状数组+二分+三分)
题目描述
$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\%$的数据满足:$1\leqslant N\leqslant 100$。
$60\%$的数据满足:$1\leqslant N\leqslant 5,000$。
$100\%$的数据满足:$1\leqslant N\leqslant 100,000$,$1\leqslant h_i\leqslant {10}^9$。
题解
$0\%$算法:
外层循环暴力枚举最高点,中间层暴力枚举最高点的高度,内层循环暴力统计答案,但是因为$h_i$的数据范围,所以理论上讲会$T$到飞起。
时间复杂度:$\Theta(n^2\max h_i)$。
期望得分:$0$分。
实际得分:$15$分。
$60\%$算法:
考虑进行优化,上面的做法肯定不行,但是我们发现,当高度过高或者过低时代价都是很大的,而当高度适中的时候代价才是最小的,这就相当与一个$a<0$的二次函数模型,那么我们就可以对高度进行三分来寻找最小值了。
时间复杂度:$\Theta(n^2\log\max h_i)$。
期望得分:$60$分。
实际得分:$60$分。
$100\%$算法1:
发现枚举最高点已经必不可少,但是统计答案的时候可以进行优化。
假设最高点为$i$,其高度为$h_i$,那么在它左边的点,有$h_j-j=h_i-i$;同理,在它右边的点,有$h_j+j=h_i+i$。
那么问题就变得简单多了,我们可已经所有的点的$h_i+i$和$h_i-i$进行排序,在枚举最高点的时候找到$h_j+j=h_i+i$的那个点所在的位置,之后分别统计两侧的点的个数和这些点的高度和,那么高度和-点的个数$\times h_i-i$的绝对值就是你这个区间之内的答案,而另一侧的区间就是找$(h_i+i)$。
利用两个树状数组进行维护即可降低时间复杂度。
时间复杂度:$\Theta(n\log n\log \max h_i)$。
期望得分:$100$分。
实际得分:$100$分。
$100\%$算法2:
使用模拟退火,代码精简,跑的飞快,但是随机性大,有可能$A$不了,多交几次就好了。
时间复杂度:$\Theta($玄学$)$。
期望得分:$100$分。
实际得分:玄学。
代码时刻
$0\%$算法:
#include<bits/stdc++.h>
using namespace std;
int n;
int a[100001],flag[100001];
int minh=1<<30,maxh;
long long ans=200209230020020923;
long long check(int h,int id)
{
long long sum=0;
for(int i=1;i<=n;i++)
{
sum+=abs(flag[i]-h);
if(h-abs(id-i)<=0)
return 200209230020020923;
}
return sum;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
minh=min(minh,a[i]);
maxh=max(maxh,a[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
flag[j]=a[j]+abs(i-j);
for(int j=minh;j<=maxh;j++)ans=min(ans,check(j,i));
}
printf("%lld",ans);
return 0;
}
$100\%$算法1:
#include<bits/stdc++.h>
using namespace std;
int n;
int a[500000];
pair<long long,int> pl[500000],pr[500000];
int rkl[500000],rkr[500000];
long long trl[500000],trr[500000];
int ctl[500000],ctr[500000];
long long ans=1LL<<62;
int lowbit(int x){return x&-x;}
void addl(int x,int w,int c)
{
for(int i=x;i<=n;i+=lowbit(i))
{
trl[i]+=w;
ctl[i]+=c;
}
}
void addr(int x,int w,int c)
{
for(int i=x;i<=n;i+=lowbit(i))
{
trr[i]+=w;
ctr[i]+=c;
}
}
pair<long long,int> suml(int x)
{
long long tmp=0;
int res=0;
for(int i=x;i;i-=lowbit(i))
{
tmp+=trl[i];
res+=ctl[i];
}
return make_pair(tmp,res);
}
pair<long long,int> sumr(int x)
{
long long tmp=0;
int res=0;
for(int i=x;i;i-=lowbit(i))
{
tmp+=trr[i];
res+=ctr[i];
}
return make_pair(tmp,res);
}
inline pair<long long,int> askl(int l,int r)
{
if(l>r)return make_pair(0LL,0);
pair<long long,int> lft=suml(l-1);
pair<long long,int> rht=suml(r);
return make_pair(rht.first-lft.first,rht.second-lft.second);
}
inline pair<long long,int> askr(int l,int r)
{
if(l>r)return make_pair(0LL,0);
pair<long long,int> lft=sumr(l-1);
pair<long long,int> rht=sumr(r);
return make_pair(rht.first-lft.first,rht.second-lft.second);
}
long long wzc(pair<long long,int> p[],int w)
{
int lft=1,rht=n,res;
while(lft<=rht)
{
int mid=(lft+rht)>>1;
if(p[mid].first>w)rht=mid-1;
else{res=mid;lft=mid+1;};
}
return res;
}
inline long long judge(int x,int h)
{
long long res;
int pos;
pair<long long,int> tmp;
res=abs(h-a[x]);
pos=wzc(pl,h-x);
tmp=suml(pos);
res+=1LL*(h-x)*tmp.second-tmp.first;
tmp=askl(pos+1,n);
res-=1LL*(h-x)*tmp.second-tmp.first;
pos=wzc(pr,h+x);
tmp=sumr(pos);
res+=1LL*(h+x)*tmp.second-tmp.first;
tmp=askr(pos+1,n);
res-=1LL*(h+x)*tmp.second-tmp.first;
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&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(int i=1;i<=n;i++)
{
rkl[pl[i].second]=i;
rkr[pr[i].second]=i;
}
for(int i=1;i<=n;i++)
addr(rkr[i],a[i]+i,1);
for(int i=1;i<=n;i++)
{
addr(rkr[i],-a[i]-i,-1);
int lft=max(i,n-i+1),rht=1000000000;
while(rht-lft>1)
{
int mid=(lft+rht)>>1;
int l=judge(i,mid-1);
int r=judge(i,mid);
if(l<r)rht=mid;
else lft=mid;
}
ans=min(ans,min(judge(i,lft),judge(i,rht)));
addl(rkl[i],a[i]-i,1);
}
printf("%lld",ans);
return 0;
}
$100\%$算法2:
#include<bits/stdc++.h> using namespace std; int n; int h[100001],flag[100001]; long long ans=200209230020020923; long long judge(int x) { int mid=(n+1)>>1; long long cnt=0; for(int i=1;i<=n;i++)flag[i]=h[i]+abs(x-i); nth_element(flag+1,flag+mid,flag+n+1); int val=flag[mid]; val=max(val,max(x,n-x+1)); for(int i=1;i<=n;i++)cnt+=abs(flag[i]-val); return cnt; } int main() { srand(time(NULL)); scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&h[i]); double t=1000; int now=(n+1)>>1; long long nowans=judge(now); while(t>1e-5) { int tmp=now+(rand()*2-RAND_MAX)*t/1000000; if(t<1){tmp=max(tmp,1);tmp=min(tmp,n);} else tmp=(tmp%n+n-1)%n+1; long long tmpans=judge(tmp); long long de=tmpans-nowans; if(de<0||exp(-de/t)*RAND_MAX>rand()){nowans=tmpans;now=tmp;} ans=min(ans,tmpans); t*=0.975; } printf("%lld",ans); return 0; }
rp++