Codeforces Round #421 (Div. 1) B. Mister B and PR Shifts(技巧)

题目链接: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\n",ans,idx);
27     return 0;
28 }
View Code

 

posted @ 2017-07-06 14:00  bin_gege  阅读(148)  评论(0编辑  收藏  举报