分析
就是说可以将多个元素从后边移动到前边,让每个数和它对应的下标差的绝对值的和最小,语文不好,凑合着理解吧
由于英语也不好,咳咳,最开始以为数是任意的,我也不认识那个排列啊,后来用的百度翻译,才发现数是一个排列。。。。
然后可以写出一个(O(n^2))的暴力,就是一个一个的移动,但肯定行不通啊,看一下数据范围,时间复杂度最多应该也就(O(n))了,(nlogn)的时间复杂度可能都够呛。
考虑那个暴力,仔细想想这样很亏,因为每次移动,只会有末尾的那个数影响比较大,所以最后再考虑那个,而对于别的数,因为只移动1,所以变化值就只有1,但它套了一个绝对值,所以会有两种不同的情况,不妨令(p_i)表示第(i)个数。
- (p_i-ileq0)
- (p_i-i>0)
第一种情况,我们最后累加的答案是(i-p_i),每次移动都是向右移动,所以这个数只会越来越大,除非,它从最后移到第一个。
第二种情况其实是最ex的,因为累加的答案是(p_i-i),而(i)的增大会导致这个数减小,那么它就有可能会小于0,它一旦小于0,累加的答案就要变化,所以这个有点棘手,为了解决这种情况,我们需要再开一个数组(d),第(i)位表示有(d_i)个数移动(i)位后会变成0,然后这个数就会被划分到情况一。
为了达到(O(n))的时间复杂度,我们需要快速计算出每种情况的数对答案的影响,可以用(cntz,sumz)分别表示是第二种情况的数有多少,答案的贡献是多少,(cntf,sumf)则表示第一种情况,在输入的时候,我们就能求出上边的几个变量,注意要记录(d)数组。
下面考虑移动,每次移动后,(sumf)会加上(cntf),(sumz)则会减去(cntz),(cntz)会减去(d_i),(cntf)会加上(d_i),这一步应该算是一个模拟吧,然后最难搞的就是最后一个数,进行第(i)次移动时,最后一位是(n-i+1)上的数,可以知道不管怎么样,这个位置的数一定属于情况一,因为它的角标是最大的,所以相减得到的数一定是非正数,然后再看它放到第一位后属于情况几就行。
TIPS
(d)数组是要开二倍的,因为下边的代码可以看一下(u+i-1)的最大值可能到(2×10^6),尽管可能用不到,但会报RE或是导致别的错误,这题也不卡空间,就多开点吧。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e6+10;
ll d[N],p[N];
int main(){
ll n,cntz=0,cntf=0,sumz=0,sumf=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&p[i]);
if(p[i]-i<=0)++cntf,sumf+=i-p[i];
else ++cntz,++d[p[i]-i],sumz+=p[i]-i;
}
ll res=sumz+sumf,pos=0;
for(int i=1;i<n;i++){
sumz-=cntz;
cntz-=d[i];
sumf+=cntf;
cntf+=d[i];
int u=p[n-i+1];
--cntf;sumf-=n-u+1;
if(u-1>0)++d[u+i-1],sumz+=u-1,++cntz;
else ++cntf;
if(res>sumz+sumf){
res=sumz+sumf;
pos=i;
}
}
printf("%lld %lld
",res,pos);
}