大概意思呢,就是求循环序列的逆序对
题解中有这样的一个思考过程
1.1 30%
O(n3) 暴力:枚举每个循环状态,再暴力计算逆序对数。
#include <cstdio> #include <iostream> using namespace std; const int N = 1000000 + 10; int n; int aa[N + N]; int main() { scanf( "%d", &n ); for( int i = 1; i <= n; i++ ) { scanf( "%d", aa + i ); aa[n + i] = aa[i]; } long long ans = 0; for( int i = 1; i <= n; i++ ) { for( int l = 0; l < n; l++ ) for( int r = l + 1; r < n; r++ ) ans += (aa[i+l] > aa[i+r]); } cout << ans << endl; }
1.2 60%
法一:
将原序列复制一份接在自己后面,形成一个长度为2n 的序列,先暴力计算[1,n] 的逆序对数,然后将这个区间向右边移动,维护答案。具体来说,就是假如我们算出了[i,j] 中的逆序对数,我们怎么算[i + 1, j + 1] 呢,其实就是加上[i+1, j] 中比a[j +1] 大的数的个数再减去[i+1, j] 中比a[i] 小的数的个数。所以我们可以暴力算那两个数量,连续n 个长度为n 的子区间的逆序数的和就是答案,复杂度O(n2)。
法二:
计算一个长度为n 的序列的逆序对的个数可以通过数据结构(树状数组或线段树)优化到O(nlogn),维护[1,i - 1] 的一个值的分布情况(加入到线段树中),每次查询这些数比a[i] 大的有多少个,再将a[i] 加入到线段树中去。所以计算n 个序列的逆序数只需要O(n2logn).
1.3 100%
将上面的法一和法二结合一下,用线段树优化法一中那个暴力,使得可以O(logn) 查询,最后复杂度O(nlogn).
#include<bits/stdc++.h> #define N 1000003 #define ll long long using namespace std; ll a[N*2],n,c[N*4]; ll lowbit(int n){return n&(-n);} void update(ll x,ll k) { while(x<=n) { c[x]+=k; x+=lowbit(x); } } ll query(ll x) { ll ans=0; while(x) { ans+=c[x]; x-=lowbit(x); } return ans; } //树状数组里存的是1~n每个数的个数(其实就是树状数组求逆序对+区间移动的思想) //对于一个数a[i],可以query它的位置query(a[i])-1就是前面比他小的数的个数, //那么前面就有i-query[a[i]]个比它小的数 int main() { // freopen("rotinv.in","r",stdin); // freopen("rotinv.out","w",stdout); int m; ll ans=0,cur=0; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%I64d",&a[i]); a[n+i]=a[i]; } for(int i=1;i<=n;i++) { ans+=(i-1)-query(a[i]);//i-1是因为这个时候只存了i-1个数,第i个数还没存la update(a[i],1);//将a[i]的个数加1 } for(int i=n+1;i<=2*n;i++) { update(a[i-n],-1);//区间移动,a[i-n]已经不在区间内了, ans+=(n-1)-query(a[i]);//那么就要把因为a[i-n]存在而失去的贡献加上 ans-=query(a[i-n]-1);//减去a[i-n]带来的贡献(这里找的是比a[i-n]小的数的个数,比其小才能构成逆序对) update(a[i],1); cur+=ans;//ans是每个序列的答案,最终的答案是每个序列的ans加起来 } printf("%I64d",cur); } /* 5 4 1 3 2 4 2 1 4 2 4 1 3 2 3 */
当然逆序对还可以用归并排序求,再以类似的思想循环序列:相当于每次把队首踢到队尾,那么与它有关的贡献就会更新
代码很好理解~
#include<bits/stdc++.h> using namespace std; #define N 1000005 #define ll long long int n,sma[N],big[N]; ll tot=0; void merge(int a[],int l,int r,int mid,int b[]) { memcpy(b+l,a+l,sizeof(int)*(r-l+1)); int i=l,k=l,j=mid+1; while(i<=mid&&j<=r) { if(b[i]<=b[j]) a[k++]=b[i++]; else a[k++]=b[j++],tot+=(mid-i+1); } while(i<=mid) a[k++]=b[i++]; while(j<=r) a[k++]=b[j++]; } void merge_sort(int a[],int l,int r,int b[]) { if(l<r) { int mid=(l+r)>>1; merge_sort(a,l,mid,b); merge_sort(a,mid+1,r,b); merge(a,l,r,mid,b); } } //上面都是归并排序 int a[N<<1],b[N<<1],c[N<<1]; ll ans=0; int main() { // freopen("rotinv.in","r",stdin); // freopen("rotinv.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),c[i]=a[i]; merge_sort(a,1,n,b); //排序后再分别找出序列中比a[i]小,大的个数 for(int i=2;i<=n;i++) if(a[i]!=a[i-1]) sma[a[i]]=i-1; for(int i=n-1;i>=1;i--) if(a[i]!=a[i+1]) big[a[i]]=n-i; for(int i=1;i<=n;i++)//不断的踢出每一个c[i] { ans+=tot; tot-=sma[c[i]]; tot+=big[c[i]]; } printf("%I64d ",ans); }
完成lalala~