【一句话题意】有一个环形的序列标号从1到n,每个元素有一个随机值ai,现在允许环形序列每个点同时顺时针转x圈,求使
Σ
i
=
1
n
∣
a
i
−
i
∣
\Sigma^n_{i=1} |ai-i|
Σi=1n∣ai−i∣最小的方案,输出最小值。
n<=1e6
【分析】由于元素是随机的,但是标号十分有规律的,所以我们转而考虑固定序列,旋转标号。
显然当标号i大于ai时,右移i,那么ai会对答案做减一的贡献;
当标号i小于等于ai但不等于1时,右移i,那么ai会对答案做加一的贡献;
当i等于1时,右移标号会从1突然变成n,暴力特判即可。
显然定义一个数组为“当i点为1,右移时答案的差值(不考虑标号为1的点)”,预处理每个点,对一个区间同时加一,再对一个区间做减一操作,再单点询问。显然树状数组O(nlogn)可过。
然而,破环成链之后直接差分再O(n)统计,可以把复杂度降到O(n)。。。。
那我考场上在 ** 做什么
【code】
考场上用树状数组AC的程序
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=2e6+1000;
LL n,a[maxn];
inline void read(LL &x){
x=0;char tmp=getchar();
while(tmp<'0'||tmp>'9') tmp=getchar();
while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
struct szsz{
int c[maxn];
int lowbit(int x){return x&-x;}
void add(int l,int r){while(l<=n) c[l]++,l+=lowbit(l);r++;while(r<=n) c[r]--,r+=lowbit(r);}
int ask(int num){int ret=0;while(num>0) ret+=c[num],num-=lowbit(num);return ret;}
}t1,t2;
int g(int x){
while(x<1)x+=n;
while(x>n)x-=n;
return x;
}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)
read(a[i]);
for(int i=1;i<=n;i++){
int l=g(i-a[i]+1),r=g(i-1);
if(a[i]>1){
if(l<=r) t1.add(l,r);
else t1.add(l,n),t1.add(1,r);
}
if(a[i]<n){
l=g(i+1),r=g(i-a[i]);
if(l<=r) t2.add(l,r);
else t2.add(l,n),t2.add(1,r);
}
}
LL ans=1LL<<60,res=0;
for(int i=1;i<=n;i++)
res+=abs(a[i]-1LL*i);
ans=res;
for(int i=1;i<n;i++){
res+=t1.ask(i),res-=t2.ask(i);
res=res-abs(a[i]-1)+abs(a[i]-n);
ans=min(ans,res);
}
cout<<ans<<endl;
return 0;
}