准备

题目描述

小 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;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值