链接:https://codeforces.com/contest/1269/problem/E
题意:给一个序列P1,P2,P3,P4....Pi,每次可以交换两个相邻的元素,执行最小次数的交换移动,使得最后存在一个子段1,2,…,k,这是题目所定义的f(k),题目要求求出所有的f(n),并依次输出。
思路:首先考虑逆序对问题,比如3 2 1 4这个序列,要使其变为1 2 3 4,最小的移动次数是这个序列中逆序对之和,2+1 = 3,逆序对是(3,2) (3,1)(2,1),但是在比如序列3 5 2 1 6 7 4 8 9,求f(4)怎么做?首先是不是把1 2 3 4这个序列聚成在一起,相连在一起,再去计算逆序对个数,两个过程所花费相加就是答案。那么这个题目就分为两个过程,1.聚合n个数字在一起。2.求逆序对的个数,两者花费相加就行。第1个过程如果使得聚合步数最少呢?其实就是求出聚合后的最中间的位置,其他所有的数字向这个位置靠近所花费的移动次数是最少的,这个过程可以用二分做。第2个过程可以用树状数组,也可以用线段树做。输入的时候记录每个数字的位置,建两个树状数组,一个树状数组维护数字出现的次数,用来求逆序对个数,另一个树状数组维护各个数字在原序列的位置。
AC代码:
1 #include<iostream> 2 #include<string> 3 #include<vector> 4 #include<cstring> 5 #include<cstdio> 6 #include<algorithm> 7 #include<cmath> 8 using namespace std; 9 typedef long long ll; 10 ll mod = 1e9+7; 11 const int maxn = 2e5+10; 12 ll t[maxn],cnt[maxn]; 13 ll pos[maxn]; 14 int n; 15 inline int lowbit(ll x){ 16 return x&(-x); 17 ///算出x二进制的从右往左出现第一个1以及这个1之后的那些0组成数的二进制对应的十进制的数 18 } 19 void add(ll *b , int x, int k) {//单点修改 20 while (x <= n) { //不能越界 21 b[x] = b[x] + k; 22 x = x + lowbit(x); 23 } 24 } 25 ll getsum(ll *b,int x) { // a[1]……a[x]的和 26 ll ans = 0; 27 while (x > 0) { 28 ans = ans + b[x]; 29 x = x - lowbit(x); 30 } 31 return ans; 32 } 33 int main(){ 34 ios::sync_with_stdio(false); 35 cin.tie(0); 36 cin>>n; 37 for(int i = 1;i<=n;i++){ 38 int t; 39 cin>>t; 40 pos[t] = i; 41 } 42 ll inv = 0; 43 for(int i = 1;i<=n;i++){ 44 inv += (i-1-getsum(t,pos[i])); 45 add(t,pos[i],1); 46 add(cnt,pos[i],pos[i]); 47 if(i==1){ 48 cout<<0<<" "; 49 continue; 50 } 51 int mid,l = 1,r = n; 52 while(l<=r){ 53 mid = (l+r)>>1; 54 if(getsum(t,mid)*2<=i){ 55 l = mid+1; 56 } 57 else{ 58 r = mid-1; 59 } 60 } 61 ll ans = 0; 62 ll cntL = getsum(t,mid); 63 ll cntR = i - cntL; 64 ll indexL = getsum(cnt,mid); 65 ll indexR = getsum(cnt,n)-indexL; 66 ans+=((mid+mid-cntL+1)*cntL)/2-indexL; 67 ans+=(indexR-((mid+1+(mid+cntR))*cntR)/2); 68 cout<<ans+inv<<" "; 69 } 70 return 0; 71 }