BZOJ 2058 [Usaco2010 Nov]Cow Photographs:逆序对【环上最小逆序对】
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2058
题意:
给你一个由1~n组成的排列,首尾相接围成一个环。
你可以任意次交换其中两个相邻位置的数字。
最终你要让所有数字顺时针递增,只有n顺时针紧邻着1。
问你最小的交换次数。
题解:
举个例子。
有一个由1~5组成的环:2 3 1 4 5
对于这个环,最终答案有若干种情况:
(1)1 2 3 4 5
(2)2 3 4 5 1
(3)3 4 5 1 2
(4)4 5 1 2 3
(5)5 1 2 3 4
每一次变化,无非是分别将1~4移到了最右边。
对于从(1)到(2)的变化,其实就是将原环中的1改为了一个比其他都大的数字(比如6),然后求了一遍逆序对。
对应到最终结果中,也就是1变成了最右边的数。
所以变化之后的答案 = 上一次的答案 - 数字i所贡献的逆序对个数 + 改成的更大的数所贡献的逆序对个数
对于每一次将数字i改为更大的数时,i一定是当前环中最小的数字。
令pos[i]为数字i在原环中出现的位置(1 <= pos <= n)。
所以:
i贡献的逆序对 = pos[i] - 1
更大的数贡献的逆序对 = n - pos[i]
即:res = res - 2*pos[i] + 1 + n
对于所有res取最小即可。
AC Code:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #define MAX_N 100005 5 #define INF 1000000000 6 7 using namespace std; 8 9 int n; 10 int a[MAX_N]; 11 int l[MAX_N]; 12 int r[MAX_N]; 13 int dat[MAX_N]; 14 int pos[MAX_N]; 15 long long ans=0; 16 long long res=0; 17 18 void read() 19 { 20 cin>>n; 21 for(int i=1;i<=n;i++) 22 { 23 cin>>a[i]; 24 pos[a[i]]=i; 25 } 26 } 27 28 void merge(int lef,int mid,int rig) 29 { 30 int n1=mid-lef+1; 31 int n2=rig-mid; 32 for(int i=0;i<n1;i++) l[i]=a[lef+i]; 33 for(int i=0;i<n2;i++) r[i]=a[mid+i+1]; 34 l[n1]=INF; 35 r[n2]=INF; 36 for(int i=0,j=0,k=lef;k<=rig;k++) 37 { 38 if(l[i]<r[j]) a[k]=l[i++]; 39 else 40 { 41 a[k]=r[j++]; 42 res+=n1-i; 43 } 44 } 45 } 46 47 void merge_sort(int lef,int rig) 48 { 49 if(lef==rig) return; 50 int mid=(lef+rig)/2; 51 merge_sort(lef,mid); 52 merge_sort(mid+1,rig); 53 merge(lef,mid,rig); 54 } 55 56 void solve() 57 { 58 merge_sort(1,n); 59 ans=res; 60 for(int i=1;i<n;i++) 61 { 62 res=res-2*pos[i]+1+n; 63 ans=min(ans,res); 64 } 65 } 66 67 void print() 68 { 69 cout<<ans<<endl; 70 } 71 72 int main() 73 { 74 read(); 75 solve(); 76 print(); 77 }