依旧是再练习下树状数组的使用:
题目大意: 给出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; }
至于速度上,跟前面的归并排序解得耗时一样,可能数据还是小了,没差别,但是在数据范围很大的时候,利用离散化还是很有用的,毕竟限于内存限制,不能一下子开那么大的数据空间。
时间: 2024-10-14 00:57:10