3 条题解

  • 1
    @ 2023-11-12 8:57:14

    不想用树状数组,我觉得归并排序更好写

    #include<iostream>
    using namespace std;
    int n,a[500005],c[500005];
    long long ans;
    void MergeSort(int l, int r){
        if (l == r)
            return;
        int  mid, i, j, k;
        mid = (l + r) / 2;
        i = l;
        j = mid + 1;
        k = l;
        MergeSort(l, mid);
        MergeSort(mid + 1, r);
        while (i <= mid and j <= r){
            if (a[i] <= a[j])  c[k++]=a[i++];
            else c[k++]=a[j++],ans+=mid-i+1;
        }   
        while (i <= mid)c[k++] = a[i++];
        while (j <= r)c[k++] = a[j++];
        for (int t = l; t <= r; t++)
            a[t] = c[t];
    } 
    int main(){
    	cin >> n; 
        for (int i = 1; i <= n; i++)
        	cin >> a[i];
        MergeSort(1, n);
        cout << ans;
        return 0;
    }
    
    • 0
      @ 2023-12-22 21:19:26

      可恶,为什么题解区没一个使用树状数组的

      归并固然好用,树状数组也必须掌握。

      这道题本质上就是在求每一个数前面有几个大于它的数,考虑树状数组。每输入一个 aia_i 就将 cai+1c_{a_i}+1 并维护,这样 sum(ai)\operatorname{sum}(a_i) 求的就是 aia_i 前面有多少个 ai\le a_i 的数,那 isum(ai)i-\operatorname{sum}(a_i) 就是 aia_i 前面有几个大于它的数,aia_i 对最终答案的贡献就求出来了。

      这是一个值域上的树状数组。但是值域太大(ai109a_i \le 10^9)数组存不下怎么办?离散化,我们只关心 aia_i 的大小关系。时间复杂度 Θ(nlogn)\Theta(n \log n)

      最后提一句:十年 OI 一场空——

      #include <bits/stdc++.h>
      #define ll long long
      using namespace std;
      int n;
      int a[500001],b[500001],c[500001];
      ll ans;
      void add(int x){
          for (int i=x;i<=n;i+=i&(-i)){
              c[i]++;
          }
      }
      ll sum(int x){
          ll cnt=0;
          for (int i=x;i;i-=i&(-i)){
              cnt+=c[i];
          }
          return cnt;
      }
      int main(){
          cin>>n;
          for (int i=1;i<=n;i++){
              cin>>a[i];
              b[i]=a[i];
          }
          sort(b+1,b+n+1);
          for (int i=1;i<=n;i++){//按顺序存入 a[i]
              int k=lower_bound(b+1,b+n+1,a[i])-b;//离散化
              add(k);//将 a[i] 加入树状数组
              ans+=i-sum(k);
          }
          cout<<ans;
          return 0;
      }
      
      • 0
        @ 2023-5-22 8:17:01

        这道题用到的是归并排序分而治之的思想,所以我们先来回顾一下归并排序的概念。 先简单看一下归并排序的代码:

        void merge_sort(int q[],int l,int r)
        {
            if(l>=r) return;
            int mid=l+r>>1;
            
            merge_sort(q,l,mid);merge_sort(q,mid+1,r);
            
            int k=0,i=l,j=mid+1;
            while(i<=mid&&j<=r)
            {
                if(q[i]<=q[j]) tmp[k++]=q[i++];
                else tmp[k++]=q[j++];
            }
            while(i<=mid) tmp[k++]=q[i++];
            while(j<=r) tmp[k++]=q[j++];
            for(i=0,j=l;j<=r;i++,j++) q[j]=tmp[i];
        }
        

        先设立一个中心点mid,进行递归处理,将数组不断分成两,最后成为单一的元素,此为分。

        然后既是让每一个元素符合我们的要求,也就是从小到大排序,此为治。

        这是归并排序,既然归并排序把数组分为两段,我们再来看看逆序对的情况

        逆序对的两个元素都在左端 逆序对的两个元素都在右端 一个元素在左端,一个元素在右端

        我们是把q[i],q[j]进行比较,把较小的元素放到一个新数组t[]中。所以我们能保证i左边的元素都是小于j的,然后i右边的元素都是大于j的,所以对于q[j]来说,满足的元素有(mid-i+1)个。

        所以我们只要在q[i]>q[j]的情况下,加上一个计数器res+=mid-i+1,就可以自然而然的完成我们的“治”,也就是寻找并记录下我们的逆序对。

        #include<iostream>
        
        using namespace std;
        
        const int N=100010;
        int q[N],t[N],n;
        long long res;//假如按照逆序对最多的情况,也就是倒序,例:5 4 3 2 1。
        				//int不能满足res。
        
        void merge_sort(int l,int r)
        {
            if(l>=r) return;
            
            int mid=l+r>>1;
            merge_sort(l,mid),merge_sort(mid+1,r);//  “分”
            
            int k=0,i=l,j=mid+1;
            while(i<=mid&&j<=r)
            {
                if(q[i]<=q[j]) t[k++]=q[i++];
                else
                {
                    t[k++]=q[j++];
                    res+=mid-i+1;//  “治”
                }    
            }
            while(i<=mid) t[k++]=q[i++];//收尾:假如左端或者右端先填完了,得把另一端的数都加上。
            while(j<=r) t[k++]=q[j++];
            
            for(i=l,j=0;i<=r;i++,j++) q[i]=t[j];
        }
        
        int main()
        {
            cin >> n;
            for(int i=0;i<n;i++) cin >> q[i];
            
            merge_sort(0,n-1);
            cout << res << endl;
            
            return 0;
        }
        

        但是上面的代码只有50分 所以我就用了树状数组 树状数组求逆序对的过程用模拟好理解一些。是用树状数组维护一个桶,在for中扫一遍的过程就是i<j的过程,在桶中log(N)维护前缀和

        下面放了一部分代码,防止抄袭

        LL tree[maxn];
        LL n;
        struct P{
        	LL val,index;
        }a[maxn];
        bool cmp(P A,P B){
        	if(A.val==B.val) return A.index<B.index;
        	return A.val<B.val;
        }
        LL add(LL x,LL d){
        	while(x<=n){
        		tree[x]+=d;
        		x+=lowbit(x);
        	}
        }
        LL sum(LL x){
        	LL sum=0;
        	while(x>0){
        		sum+=tree[x];
        		x-=lowbit(x);
        	}
        	return sum;
        }
        int main(void)
        {
          cin.tie(0);std::ios::sync_with_stdio(false);
          cin>>n;
          for(LL i=1;i<=n;i++){
          	cin>>a[i].val;a[i].index=i;
          }
        }
        

        image 又通过一道题

        • 1

        信息

        ID
        76
        时间
        1000ms
        内存
        125MiB
        难度
        6
        标签
        递交数
        218
        已通过
        67
        上传者