题目链接:Codeforces Round #421 (Div. 1) B. Mister B and PR Shifts
题意:
给你n个数,定义f(x)=∑abs(p[i]-i),即第i个数与i的差值和。
每次可以将最后一个数放在第一个数,现在问你最小的f(x)是多少,和操作多少次。
题解:
这是一个比较有技巧性的题目。
就是考如何来维护每个数的偏移量。
开一个数组cnt,cnt[i]表示原始数列的一个数在实际位置的左边并且距离实际位置为i的个数。
l表示一个数在实际位置的左边的个数,r表示一个数在实际位置右边的个数。
每次移动的时候l-=cnt[i-1],r+=cnt[i-1],即第i次移动的时候要将对应偏移量的数更新。
相当于偏移量为i-1的数从左边跑到了右边。
显然不考虑最后一个数的时候每次移动,整体的答案变化为sum=sum-l+(r-1)。(减1是因为不考虑最后一个数)
然后我们再特判最后一个数。从最后一个移到第一个,对答案的贡献为abs(a[n]-n)+a[n]-1。
这里的n随着i的变化而变化。
移动完后,我们还得维护一下l,r,cnt。
由于将最后一个数移到了第一个位置。显然这个数是在实际位置的左边(为了方便,等于实际位置也算在左边),所以l++,r--。
由于cnt[i]记录的是原始数列的偏移量,第i次将最后一个数移到了第一个位置,cnt[a[n]-1+i]++。
表示从第i次开始,偏移量为a[n]-1的数多了一个。
细节上还需要自己理解理解。
1 #include<bits/stdc++.h> 2 #define F(i,a,b) for(int i=(a);i<=(b);++i) 3 using namespace std; 4 typedef long long ll; 5 6 const int N=2e6+7; 7 int cnt[N],a[N],idx,l,r,n; 8 ll ans,sum; 9 10 int main(){ 11 scanf("%d",&n); 12 F(i,1,n) 13 { 14 scanf("%d",a+i); 15 if(a[i]>=i)l++,cnt[a[i]-i]++;else r++; 16 ans+=abs(a[i]-i); 17 } 18 sum=ans,idx=0; 19 F(i,0,(n-1)) 20 { 21 l-=cnt[i],r+=cnt[i]; 22 sum=sum-l+r-abs(a[n-i]-n)+a[n-i]-2; 23 cnt[a[n-i]+i]++,l++,r--; 24 if(sum<ans)ans=sum,idx=i+1; 25 } 26 printf("%lld %d ",ans,idx); 27 return 0; 28 }