依旧是再练习下树状数组的使用:
题目大意: 给出N个数,这些数可以把后面的删掉然后放到最前面形成新的序列
可得到的N种情况,求出这N种情况哪种的逆序数最小
解题思路: 先求出第一个序列的逆序数,然后用很巧妙的办法求下一个序列的逆序数,直到全部求出
序列 4 5 2 1 3 6 ,此序列的逆序数为7,它等到的下一个序列为 5 2 1 3 6 4
看这个新序列的产生过程,首部删除4,尾部添加4
删除4,必然会使得这个序列的逆序数减少(4-1)个,因为4前面必定有4-1个数小于4
添加4,必然会使得这个序列的逆序数增加(6-4)个,因为4后面必定有6-4个数大于4
由此推出公式,假设移动的数为m,序列的逆序数=上一序列逆序数-(m-1)+(N-m)
先要离散化出来(这点似乎不需要,但是我还是离散了),因为给的数据范围就是0~n-1
<pre name="code" class="cpp">//树状数组
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int n;
#define MAX 5010
typedef struct nano{
int val;
int order;
}node;
node a[MAX];
int aa[MAX];
int c[MAX];
int cmp(node a,node b){
return a.val<b.val;
}
void update(int x){
while(x<=n){
c[x]++;
x+=(x&-x);
}
}
int sum(int x){
int s=0;
while(x>=1)//一定是1!0就死循环了
{
s+=c[x];
x-=(x&-x);
}
return s;
}
int main(int argc, char *argv[])
{
// freopen("1394.in","r",stdin);
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i].val);
a[i].order=i;
}
/*for(int i=1;i<=n;++i)
printf("%d ",a[i].val);*/
sort(a+1,a+n+1,cmp);
//printf("\n");
for(int i=1;i<=n;++i)
aa[a[i].order]=i;
long long ans=0;
/* printf("------\n");
for(int i=1;i<=n;++i)
{
printf("%d ",aa[i]);
}*/
//printf("\n------\n");
memset(c,0,sizeof(c));
for(int i=1;i<=n;++i){
update(aa[i]);
ans+=(i-sum(aa[i]));
}
long long MIN=ans;
for(int i=1;i<=n;++i){
ans=ans-(aa[i]-1)+(n-aa[i]);
if(ans<MIN)MIN=ans;
}
printf("%lld\n",MIN);
}
return 0;
}
至于速度上,跟前面的归并排序解得耗时一样,可能数据还是小了,没差别,但是在数据范围很大的时候,利用离散化还是很有用的,毕竟限于内存限制,不能一下子开那么大的数据空间。