1. 程式人生 > >nlogn求逆序對&&陌上花開

nlogn求逆序對&&陌上花開

www 計數器 比較 stk cst 運用 完成後 spa 數組

前置:
nlogn逆序對:
前一個小時我還真的不會這個Orz
這裏運用歸並排序的思想。
對於一個序列,我們把它先分開,再合並成一個有序序列。
引自https://blog.csdn.net/qq_30189255/article/details/50937307

假設f(i,j)為i到j號元素中的逆序對個數,取一個分割點k,假設s(i,j,k)表示以k為分割點,第一個元素在i到k中,第二個元素在k+1到j中形成的逆序對數。那麽我們就得到一個遞歸式:f(i,j)=f(i,k)+f(k+1,j)+s(i,j,k)。很自然的與歸並排序聯系到了一起,對於更小規模的f可以遞歸求解,如果對於a數組的i到k以及k+1到j兩個部分元素均為有序的情況,那麽對於當a[j]<a[i]的情況,必然有a[j]<a[i..k],即a[j]和i到k號元素都形成逆序對,此時只要將s(i,j,k)加上k-i+1就可以了(i到k之間的元素個數),這個過程很像歸並排序的Merge的過程——比較當前i和j的狀態並放入較小的。於是我們就得到了算法,和歸並排序一起操作:外圍設置一個計數器count,每次歸並過程分為分割與合並兩個部分,分割照常處理,在合並部分中的if (a[i]>a[j]) b[++l]=a[++j];中加入一個計數過程,即cnt+=k-i+1。這樣當排序完成後,統計也就完成了。

代碼如下

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n,a[50005],b[50005];
int merge(int l,int r) {
    int ans=0;
    if(l>=r) return 0;
    int mid=l+r>>1;
    ans+=merge(l,mid),ans+=merge(mid+1,r);
    int i=l,j=mid+1,k=0,cnt=0;
    while(i<=mid&&j<=r) {
        if(a[i]<=a[j]) b[k++]=a[i++];
        else cnt+=mid-i+1,b[k++]=a[j++];
    } 
    while(j<=r) b[k++]=a[j++];
    while(i<=mid) b[k++]=a[i++];
    for(k=0;k<r-l+1;k++) {
        a[l+k]=b[k];
    }
    return cnt+ans;
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    cout<<merge(1,n);
}

這種問題又叫二維偏序,因為有兩個維度:位置,數值

那麽三維偏序呢?

IOI2019金牌選手GhostCai發明的czq分治
dalao陳丹琦發明的算法叫cdq分治
例題:陌上花開https://www.luogu.org/problemnew/show/P3810
cdq+樹狀數組 引自 https://blog.csdn.net/reverie_mjp/article/details/52462651

【對於本題來說,每個操作包含三維,首先按第一維關鍵字排序,並去重,數組中記錄相同的花有多少朵。然後CDQ分治處理,處理時,將[l,mid]區間和[mid+1,r]區間分別按第二維關鍵字排序,並用樹狀數組以第三維為下標,維護每一朵花的出現次數。每一次處理[l,mid]對[mid+1,r]的影響時,只需考慮第二維的影響即可(因為[l,mid]區間的x一定小於[mid+1,r]區間的x,而第三維用樹狀數組維護也不需要考慮),當第二維符合要求時,將它的影響加入樹狀數組中。每查找完[mid+1,r]區間的一個操作,就更新答案】

#include <cstdio>
#include <algorithm>
using namespace std;
int t[500005];
int n,m;
const int N=500005;
struct Node {
    int x,y,z,cnt,ans;
    bool operator == (const Node &rhs)const {
        return x==rhs.x&&y==rhs.y&&z==rhs.z;
    }
} a[N],stk[N];
bool cmp(Node x,Node y) {
    return x.x==y.x?(x.y==y.y?x.z<y.z:x.y<y.y):x.x<y.x;
}
void add(int x,int y) {
    for(int i=x; i<=m; i+=i&-i) 
        t[i]+=y;
}
int ask(int x) {
    int res=0;
    for(int i=x; i; i-=i&-i) 
        res+=t[i];
    return res;
}
void merge(int l,int r) {
    if (l==r) 
        return;
    int mid=l+r>>1;
    merge(l,mid);
    merge(mid+1,r);
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r) {
        while(i<=mid&&a[i].y<=a[j].y) add(a[i].z,a[i].cnt),stk[++k]=a[i++];
        while(j<=r&&a[j].y<a[i].y) a[j].ans+=ask(a[j].z),stk[++k]=a[j++];
    }
    while(i<=mid) add(a[i].z,a[i].cnt),stk[++k]=a[i++];
    while(j<=r)  a[j].ans+=ask(a[j].z),stk[++k]=a[j++];
    for(int i=l; i<=mid; i++) add(a[i].z,-a[i].cnt);
    for(int i=l; i<=r; i++) a[i]=stk[i-l+1];

}
int d[N];
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++) {
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z),a[i].cnt=1;
    }
    sort(a+1,a+1+n,cmp);
    int cnt=0;
    for(int i=1; i<=n; i++) {
        if(a[i]==a[i-1]&&i!=1) a[cnt].cnt++;
        else cnt++,a[cnt]=a[i];
    }
    swap(n,cnt);
    merge(1,n);
    for(int i=1; i<=n; i++) d[a[i].ans+a[i].cnt]+=a[i].cnt;
    for(int i=1; i<=cnt; i++) printf("%d\n",d[i]);
}

nlogn求逆序對&&陌上花開