题目链接: 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;
}
}