CF819B Mister B and PR Shifts
分析
就是说可以将多个元素从后边移动到前边,让每个数和它对应的下标差的绝对值的和最小,语文不好,凑合着理解吧
由于英语也不好,咳咳,最开始以为数是任意的,我也不认识那个排列啊,后来用的百度翻译,才发现数是一个排列。。。。
然后可以写出一个\(O(n^2)\)的暴力,就是一个一个的移动,但肯定行不通啊,看一下数据范围,时间复杂度最多应该也就\(O(n)\)了,\(nlogn\)的时间复杂度可能都够呛。
考虑那个暴力,仔细想想这样很亏,因为每次移动,只会有末尾的那个数影响比较大,所以最后再考虑那个,而对于别的数,因为只移动1,所以变化值就只有1,但它套了一个绝对值,所以会有两种不同的情况,不妨令\(p_i\)表示第\(i\)个数。
- \(p_i-i\leq0\)
- \(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\n",res,pos);
}