POJ-2299(线段树或树状数组或归并排序)

题目链接: Ultra-QuickSort

第一种解法(线段树)

思路

刚刚学线性代数学到的逆序数,用多重循环果然超时,刚开始的时候完全没有线段树的思路,后来看了别人的思路,发现真的妙啊,开心的飞起来,虽然我后面又因为把小括号写成中括号的问题WA了一晚上。比如说9 1 0 5 4这个序列,我们记录一下他们的序号位置,然后再排个序:

0 1 4 5 9
在原序列中的位置 3 2 5 4 1

开始我们把每个节点的数值都设置为1;
我们从最小数0开始看,他原来的位置是3,说明它前面比它大的有两个数,我们查询从1到(位置-1),也就是1到2,查询这段区间的总和为2;然后我们把这个位置的和-1,再依次往后看次小数。我们把所有查询的数字加起来就是总的逆序数了。

#include 
#include 
#include 
#include 
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=500001;
int addmark[maxn<<2],seqtree[maxn<<2];
struct node{
     int value,id;
}a[maxn];
int cmp(node a,node b)
{
    return a.value<b.value;
}
void maketree(int node,int begin,int end)
{
    if(begin==end)
    {
        seqtree[node]=1;
        return;
    }
    int mid=(begin+end)/2;
    maketree(node<<1,begin,mid);
    maketree(node<<1|1,mid+1,end);
    seqtree[node]=seqtree[node<<1]+seqtree[node<<1|1];
}
void update(int node,int begin,int end,int pos,int grade)
{
    if(pos<begin || pos>end)
        return;
    if(begin==end && begin==pos)
    {
        seqtree[node]+=grade;
        return;
    }
    int mid=(begin+end)/2;
    update(node<<1,begin,mid,pos,grade);
    update(node<<1|1,mid+1,end,pos,grade);
    seqtree[node]=seqtree[node<<1]+seqtree[node<<1|1];
}
int query(int node,int begin,int end,int l,int r)
{
    int sum=0;
    if(l>end || r<begin)
        return 0;
    if(l<=begin && r>=end)
        return seqtree[node];
    int mid=(begin+end)/2;
    sum+=query(node<<1,begin,mid,l,r);
    sum+=query(node<<1|1,mid+1,end,l,r);
    return sum;
}
int main()
{
    int n,i;
    long long sum;
    while(~scanf("%d",&n) && n)
    {
        sum=0;
    maketree(1,1,n);
    for(i=1;i<=n;i++)
    {
    cin>>a[i].value;
    a[i].id=i;
    }
    sort(a+1,a+n+1,cmp);
    for(i=1;i<=n;i++)
    {
        sum+=query(1,1,n,1,a[i].id-1);
        update(1,1,n,a[i].id,-1);
    }
    cout<<sum<<endl;
    }
}

第二种解法(归并排序)

思路

归并排序需要来回不断交换区间内的元素,我们只需要记录k指针和j指针两者的差值,就是在此段区间内比j指向的这个数大的数的个数,我们把这个值累加就可以。

#include 
#include 
#include 
#include 
using namespace std;
int a[500010],b[500010];
long long sum;
void mergei(int a[],int low,int mid,int high)
{
    int i,j,k;
    i=low,j=mid+1,k=low;
    while(i<=mid && j<=high)
    {
        if(a[i]>a[j]){
                sum+=j-k;
                b[k++]=a[j++];
        }
        else {
                b[k++]=a[i++];
        }
    }
    while(i<=mid)
        b[k++]=a[i++];
    while(j<=high)
        b[k++]=a[j++];
    for(i=low;i<=high;i++)
        a[i]=b[i];
}
void merge_sort(int a[],int low,int high)
{
    if(low<high)
    {
        int mid=(low+high)/2;
        merge_sort(a,low,mid);
        merge_sort(a,mid+1,high);
        mergei(a,low,mid,high);
    }
}
int main()
{
    int n,i;
    while(scanf("%d",&n)!=EOF && n)
    {
    sum=0;
    for(i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    merge_sort(a,1,n);
    cout<<sum<<endl;
    }
}

第三种解法(树状数组)

思路

树状数组充分利用二进制的特点组成,每次查询i可以查询出前i项的和,我们的思路是先把输入的数据离散化一下,然后以离散后的数据进行更新和查询,我们要查询出当前数字前面有几个比他大的数字,我们可以反着求,用前面的数字个数-比当前数字小的个数。而这个小的个数就是用树状数组的查询,开始时,我们把tree数组初始化为0,每当查询完毕一个数,我们就把tree[这个数]加一,这样我们只需要看看当前数字前面总共有多少个1就行了。

#include 
#include 
#include 
#include 
using namespace std;
int tree[500001],n;
struct node{
  int id,value,pos;
}a[500001];
int lowbit(int x)
{
    return (x&(-x));
}
void update(int x,int k)
{
    int i;
    for(i=x;i<=n;i=i+lowbit(i))
    {
        tree[i]+=k;
    }
}
int query(int x)
{
    int i,sum=0;
  for(i=x;i>0;i=i-lowbit(i))
  {
      sum+=tree[i];
  }
  return sum;
}
int cmp(node a,node b)
{
    return a.value<b.value;
}
int cmp1(node a,node b)
{
    return a.id<b.id;
}
int main()
{
    int i;
    long long sum;
    while(cin>>n && n)
    {
    sum=0;
    memset(tree,0,sizeof(tree));
    for(i=1;i<=n;i++)
    {
        cin>>a[i].value;
        a[i].id=i;
    }
    sort(a+1,a+n+1,cmp);
    for(i=1;i<=n;i++)
    {
        a[i].pos=i;
    }
    sort(a+1,a+n+1,cmp1);
    for(i=1;i<=n;i++)
    {
        sum+=i-1-query(a[i].pos);
        update(a[i].pos,1);
    }
    cout<<sum<<endl;
    }

}

你可能感兴趣的:(线段树,树状数组,归并排序)