线段数 --- (单点更新、求逆序对)
Minimum Inversion Number
Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u
Description
The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.
For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:
a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)
You are asked to write a program to find the minimum inversion number out of the above sequences.
For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:
a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)
You are asked to write a program to find the minimum inversion number out of the above sequences.
Input
The input consists of a number of test cases. Each case consists of two
lines: the first line contains a positive integer n (n <= 5000); the
next line contains a permutation of the n integers from 0 to n-1.
Output
For each case, output the minimum inversion number on a single line.
Sample Input
10
1 3 6 9 0 8 5 7 4 2
Sample Output
16
用线段树求逆序对的水题,维护的是区间内整数的个数,每次插入 x 时,查询在 x 的前面比小的数的个数,并计算出比 x 大的数的个数 cnt[x] ,最后将 cnt 累加,即为逆序数;
将队首的数放到队尾后逆序数改变:n-1-a[i], a[i] 为原始序列中第 i 个数的值;
# include <stdio.h> # include <string.h> # define N 50010 int n, D; int sum[4 * N], cnt[N], a[N]; void update(int i) { for (; i ^ 1; i >>= 1) { sum[i >> 1] = sum[i] + sum[i ^ 1]; } } int query(int s, int t) { int ret; ret = 0; s += D-1, t += D+1; for ( ; s ^ t ^ 1; s >>= 1, t >>= 1) { if (~s & 0x1) ret += sum[s+1]; if ( t & 0x1) ret += sum[t-1]; } return ret; } void init(void) { int i; for (D = 1; D < n+2; D <<= 1) ; memset(sum, 0, sizeof(sum[0])*2*D+5); memset(cnt, 0, sizeof(cnt[0])*n+5); memset(a, 0, sizeof(a[0])*n+5); for (i = 1; i <= n; ++i) { scanf("%d", &a[i]); cnt[i] = i - 1 - query(0, a[i]); ++sum[a[i]+D], update(a[i]+D); } } void solve(void) { int rev, i, min; rev = 0; for (i = 1; i <= n; ++i) { rev += cnt[i]; } min = rev; for (i = 1; i <= n; ++i) { rev += n-1-2*a[i]; if (min > rev) min = rev; } printf("%d\n", min); } int main() { while (~scanf("%d", &n)) { init(); solve(); } return 0; }
树状数组实现:
#include<stdio.h> #include<string.h> #define min(a,b)(a)<(b)?(a):(b); int v[5002]; int l[5002]; int n; int lowbit(int x) { return (x)&(-x); } void update(int pos) { while(pos<=n) { l[pos]+=1; pos+=lowbit(pos); } } int getsum(int x) { int sum=0; while(x>0) { sum+=l[x]; x-=lowbit(x); } return sum; } int main() { int res,p,i,sum; while(scanf("%d",&n)!=EOF) { sum=0; memset(l,0,sizeof(l)); for(i=0;i<n;i++) { scanf("%d",&v[i]); v[i]++; sum+=(getsum(n)-getsum(v[i])); update(v[i]); } res=sum; for(i=0;i<n;i++) { sum+=n-v[i]-v[i]+1; res=min(res,sum); } printf("%d\n",res); } return 0; }
归并排序实现:
#include <stdio.h> long long left[100001], right[100001]; long long count; void merge(long long* a, long long p, long long q, long long r) { long long i, j, k, n1, n2; n1 = q-p+1; n2 = r-q; for (i=0; i<n1; ++i) { left[i] = a[p+i]; } for (i=0; i<n2; ++i) { right[i] = a[q+i+1]; } left[n1] = right[n2] =2147483647; i = j = 0; for (k=p; k<=r; ++k) { if (left[i] <= right[j]) { a[k] = left[i]; i++; } else { a[k] = right[j]; j++; count += n1-i; } } return; } void mergesort(long long* a, long long p, long long r) { long long q; if (p < r) { q = (p+r)/2; mergesort(a, p, q); mergesort(a, q+1, r); merge(a, p, q, r); } return ; } long long n,i, t, a[100001]; int main() { while (scanf("%I64d", &n)!=EOF) { count = 0; for (i=0; i<n; ++i) { scanf("%I64d", &a[i]); } mergesort(a, 0, n-1); printf("%I64d\n", count); } }
对比了几种求逆序对的方法后,发现还是归并好用,既快代码又短.