CF820D
思维题
题意:
给出一个\(n\)元素排列\(p[]\),定义数组\(p[]\)的误差值为\(\sum\limits_{i=1}^{i=n} |p[i]-i|\).每次操作都把下标为\(n\)的数放到下标为\(1\)的位置,其他数依次右移,问在通过几次操作后能使得误差值最小
较麻烦做法:差分
正解是差分,设\(d[i]\)为i次操作后的误差值,考虑\(p[j]\)对\(d[i]\)的贡献,发现i是一段连续的区间,即区间\([l,r]\)同时加上某个等差数列。
区间\([l,r]\)加上\(k*(x-l)+b\)。\((l≤x≤r)\) 设置两个数列\(d[i],f[i]\)。
- 对\(d[l]+=b,d[r+1]-=b\).剩下\(k*(x-l)\).
- 对\(f[l+1]+=k. f[r+1]-=k\) 对\(f\)求一次前缀和,
- 在\(d[r+1]\)减去\((r-l+1)\)个\(k\)表示该等差数列的结束.
- 然后对\(d[i]\)加上\(f[i]\)的前缀和,\(d[i]\)在加上本身的前缀和即可得到最后真正的\(d[i]\).
\(f[i]\)的前缀和就是\(i\)位置最终的\(k\)值,再求一次前缀就是\(1\)到\(i\)的\(k\)之和,但由于我们只需要\([l+1,x]\)的\(k\)值之和,所以还要将多出的减掉
不过我们还有更简单的解法
法二:直接模拟:
注意到对于每个p[i],使得\(p[i]==i\)的时间点只有一个,计算出这个时间点,若p[i]!=1,让该时间内由p[i]>i变成p[i]==i的数++
考虑每次右移操作造成的影响,可以看做\(1\)到\(n-1\)位置的数\(i+1\),\(n\)位置\(i=1\),若能维护出实时的大于\(0\)的个数和小于\(0\)的个数,再单独考虑最后一个数,就可以动态的计算答案。由于使得\(p[i]==i\)的时间点只有一个,故容易预处理每个时间的变化量。
//细节太繁琐,我崩溃了
//果然还是太菜了
#include<bits/stdc++.h>
using namespace std;
#define go(i,a,b) for(int i=a;i<=b;++i)
#define com(i,a,b) for(int i=a;i>=b;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define fo(i,a) for(int i=0;i<a;++i)
#define il inline
const int N=1e6+5;
typedef long long ll;
int p[N],cp=0,cn=0,mink,k,n;
ll sum,ans;
int idx[N];
il void read(int &x){
x=0;char c=getchar(),f=1;
while(!isdigit(c)){ if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)){ x=x*10+c-'0'; c=getchar(); }
x*=f;
}
int main(){
//freopen("input.txt","r",stdin);
read(n);
go(i,1,n) read(p[i]);
go(i,1,n){
idx[(p[i]-i+n)%n]++;
if(p[i]>i)cp++;
if(p[i]<=i)cn++;
sum+=abs(p[i]-i);
}
mink=0;
ans=sum;
go(k,1,n-1){
sum=sum+cn-1-cp;
sum+=2*p[(n-k)%n+1]-1-n;
cp++,cn--;
cp-=idx[k],cn+=idx[k];
if(sum<ans){
mink=k;
ans=sum;
}
}
cout<<ans<<' '<<mink;
return 0;
}