题目描述
小 W 准备参加 PION,但是作为一个没有经验的新手,他在考试前一天思考怎样分配考 试的时间。 一场 PION 的过程可以看成由 N 个部分组成,小 W 计划在第 i 个部分使用 a[i]单位的时 间。但是经过调查,他发现第 i 个部分使用 i 个单位时间的同学成绩比较出色,所以他定义 一个计划的“差异值”为∑|a[i]-i|。 由于第二天要考试了,所以他不能过多修改计划,唯一可以进行的操作是把所有数向 前循环移动一位,即:令 a’[n]=a[1],a’[i]=a[i+1],并且他可以进行任意次操作。现在小 W 请你告诉他最小的“差异值”是多少。
输入输出格式
输入格式:
第一行一个整数 N。 第二行 N 个整数 a[i]。
输出格式:
一行一个整数表示“差异值”的最小值。
输入输出样例
输入样例#1: 复制
3 2 3 1
输出样例#1: 复制
0
输入样例#2: 复制
6 4 2 2 4 2 5
输出样例#2: 复制
6
说明
对于 50%的数据,满足 N<=1000; 对于 80%的数据,满足 N<=100000; 对于 100%的数据,满足 N<=2000000,1<=a[i]<=N。
给出一个长度为 N 的数组,要求进行任意次循环位移,使得∑|a[i]-i|最小。 由于是循环位移,所以移动 N 次之后方案会循环,那么只要考虑前 N 次移动就可以了。如 果把这个绝对值的符号分情况讨论,对于一个数,它在某几次循环位移的时候会使得答案+1, 在某几次循环位移的时候会使得答案-1(从 1 移动到 N 的情况可以单独考虑),并且+1 和1 的时刻分别是一段区间。那么可以维护每次移动后相对于上次的变化量,维护这个变化量 的时候需要区间+1/-1,查询每个位置的和。可以使用线段树,但是是在所有操作之后进行 询问,所以可以用差分。
#include<bits/stdc++.h>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN=2000005,INF=1e9;
long long n,a[MAXN<<1],b[MAXN<<1];
long long ans=0,ans2=0;
long long res=0;
int main()
{
ios::sync_with_stdio(false);
int i,j;
cin>>n;
f(i,1,n){
cin>>a[i];
ans+=abs(a[i]-i);
if(a[i]-i<0){
b[1]--;
if(a[i]>1){
b[i-a[i]+1]+=2;
b[i]--;
}
else b[i]++;
b[i+1]--;
}
else{
if(i!=1){
b[1]++;
b[i]--;
}
if(a[i]<n){
b[i+1]--;
b[i+n-a[i]+1]+=2;
}
else{
b[i+1]++;
}
}
}
// cout<<ans<<endl;
f(i,1,n-1){
b[i]+=b[i-1];
// cout<<i<<" "<<b[i]<<" "<<abs(a[i]-n)-abs(a[i]-1)<<"GG"<<endl;
res+=b[i]+abs(a[i]-n)-abs(a[i]-1);
// cout<<res<<"GG"<<endl;
ans2=min(ans2,res);
}
cout<<ans+ans2<<endl;
return 0;
}
写法二:联想绝对值函数,斜率。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ri register int
ll A[4000010],n,ans,cg[4000010],delta,now;
int main()
{
scanf("%d",&n);
for(ri i=1;i<=n;i++)
{scanf("%d",&A[i]);A[i+n]=A[i];}
for(ri i=1;i<=2*n+1;i++)
{if(A[i]<=i)cg[i-A[i]]+=2;}
for(ri i=1;i<=n;i++)
{
now+=abs(A[i]-i);
if(A[i]>=i)delta++;else delta--;
}
ans=now;
for(ri i=1;i<n;i++)
{
now-=abs(1-A[i]);now+=abs(n-A[i+n]);delta-=2;
now+=delta+1;delta+=cg[i];ans=min(ans,now);
}
printf("%lld",ans);
return 0;
}