题目链接:http://codeforces.com/contest/1269/problem/E
给定一个1到n的排列,定义一个移动操作为:交换相邻的两个元素。
现在定义一个函数f(x),表示在原排列中,通过交换操作,形成一个1,2,3....x的排列的子串,需要的最小操作步骤。
子串意味着这个排列必须是相邻的。
现在你需要求出f(1),f(2),f(3)......f(n)。
我们考虑相邻的情况。
ans{1 2 3}=0
ans{1 3 2}=1;
ans{2 1 3}=1;
ans{3 2 1}=3;
ans{3 1 2}=2;
ans{4 1 2 3}=3;
。。。
我们发现,在元素相邻的情况下,答案一定是与逆序对有关的(因为最后排列不存在逆序对),并且每一次操作都可以消除一对逆序对,所以,相邻的答案就是逆序对的个数。
现在考虑不相邻的情况,如何移动才能使操作步数最少呢?答案应该是先将i个元素移动到一块儿,变成相邻的情况,再交换逆序对,这样才是最优的,因为这样操作,对于中间的每个不是当前排列的元素,他只会越来越往外面走,而不是混在i个元素中,造成操作的浪费。
所以思路就明确了起来:
首先我们将所有元素移动到一块儿,移动到哪儿合适呢?位置mid应该是左右两边的元素个数尽量相等,这样我们就可以二分中间位置mid了;
其次,说到逆序对,就需要用到树状数组,将元素插入到pos[i]的位置,这样就可以O(logn)的计算当前i元素造成的逆序对个数了(这样就可以累加);
然后,我们需要计算将元素尽量向中间点mid移动的花费,我们可以分为两部分:mid左边的部分,设个数为cnt(可以用树状数组O(log)求),假设cnt=2,如下图:
pos2移动到mid,需要花费mid-pos2;pos1移动到mid-1,需要花费mid-pos1-1;
如果还有pos3,pos4,花费也是同样计算的。假设位置pos之和为sum,花费为
这就是左边的花费。
右边的同理,尽量往mid+1这个位置移动,假设右边的所有位置之和为sum,个数为cnt,花费为
最后,别忘了逆序对的花费,逆序对可以O(n)递推,每次求的复杂度为O(logn).
最后复杂度大概是O(nlog^2n)。
具体细节可以看代码:
#include
#define int long long
using namespace std;
const int maxn=2e5+10;
int sum1[maxn];//数的个数
int sum2[maxn];//数的下标之和
int n;
int lowbit(int x){
return x&-x;
}
void add(int *sum,int x,int v){
while(x<=n){
sum[x]+=v;
x+=lowbit(x);
}
}
int query(int *sum,int x){
int res=0;
while(x>0){
res+=sum[x];
x-=lowbit(x);
}
return res;
}
int a[maxn];
int pos[maxn];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
pos[a[i]]=i;
}
int ans1=0;//逆序对的和
for(int i=1;i<=n;i++){
ans1+=i-1-query(sum1,pos[i]);//查询在i之前且比i小的数,逆序对
//将i插入树状数组
add(sum1,pos[i],1);
add(sum2,pos[i],pos[i]);
int mid,l=1,r=n;//二分需要靠拢的最中间的位置
while(l<=r){
mid=(l+r)>>1;
if(query(sum1,mid)*2<=i){
l=mid+1;
}
else{
r=mid-1;
}
}
//printf("debug %lld\n",mid);
//printf("debug %lld\n",ans1);
int ans2=0;
//将mid左边的数靠拢到mid附近的花费
int cnt=query(sum1,mid),sum=query(sum2,mid);
ans2+=mid*cnt-sum-cnt*(cnt-1)/2;
//将mid右边的数靠拢到mid附近的花费
cnt=i-cnt,sum=query(sum2,n)-sum;
ans2+=sum-cnt*(mid+1)-cnt*(cnt-1)/2;
printf("%lld ",ans1+ans2);
}
puts("");
return 0;
}