大江东去,浪淘尽,千古风流人物。故垒西边,人道是,三国周郎赤壁。乱石穿空,惊涛拍岸,卷起千堆雪。江山如画,一时多少豪杰。遥想公瑾当年,小乔初嫁了,雄姿英发。羽扇纶巾,谈笑间,樯橹灰飞烟灭。故国神游,多情应笑我,早生华发。人生如梦,一尊还酹江月。

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]\)

  1. \(d[l]+=b,d[r+1]-=b\).剩下\(k*(x-l)\).
  2. \(f[l+1]+=k. f[r+1]-=k\)\(f\)求一次前缀和,
  3. \(d[r+1]\)减去\((r-l+1)\)\(k\)表示该等差数列的结束.
  4. 然后对\(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;
}
posted @ 2019-11-04 10:10  White_star  阅读(109)  评论(0编辑  收藏  举报
}