HDU 1394 Minimum Inversion Number(线段树)
Time limit | 1000 ms |
Memory limit | 32768 kB |
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.
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
题目分析
题目的要求是:输入一段数,在以下这些序列
a1, a2, ..., an-1, an (最初的序列)
a2, a3, ..., an, a1 (第一次将上一个序列的第一个数移动到最后面去)
a3, a4, ..., an, a1, a2 (第二次将上一个序列的第一个数移动到最后面去)
..............................
an, a1, a2, ..., an-1 (第n-1次将上一个序列的第一个数移动到最后面去)
中统计一类组合(位置在前面的数的值 比 位置在后面的数的值大)在某个序列中出现的总次数次数sum,并输出这n个序列中最小的那一个sum.
对于序列中第x位数value[x],如果在序列的第x位之前的数有比value[x]大的数,那么就可以凑成一个这样的组合。对于初始序列,由于我们是按照顺序输入的数据,所以对于刚刚输入的一个数value[i],我们只需要找到已经输入的数(也就是序号比这个数的小的数)中值比value[i]大的数的个数即可,按照这样的方法,我们输入完初始数据后,就会的到初始序列的总组合数。
还剩下的n-1个序列,每次将当前序列的第一个数移动到最后一个就可以得到另外一个序列。这样会造成比移动的这个数大的数可以和他组成一个组合(因为移动的数位置最靠后,但是值比这些数小),这样的组合有n-1- value[i] 个,然而在之前位置上,比这个数小的数和其形成一个组合(移动前位置在最前面,所以后面的数只要比这个数小,就可以形成一个组合),一共有value[i]个,所以移动的序列中总组合数为sum = sum + (n-1 - value[i]) - value[i] 。
代码区
#include<iostream> #include<cstdio> #include<string> #include<cstring> #include<algorithm> #include<vector> #include<queue> #include<sstream> #include <iterator> #include<cmath> #include <map> #define sf(a) scanf("%d",&a) #define sff(a,b) scanf("%d%d",&a,&b) #define sfff(a,b,c) scanf("%d%d%d",&a,&b,&c) #define sffff(a,b,c,d) scanf("%d%d%d%d",&a,&b,&c,&d) #define slf(a) scanf("%lld",&a) #define ssf(str) scanf("%s",str) #define scf(ch) scanf("%c",&ch) #define mem(s,data) memset(s,data,sizeof(s)) #define forL(i,a, b) for (int i = a; i <= b; i++) #define forL(j,a, b) for (int j = a; j <= b; j++) #define forR(i,a, b) for (int i = a; i >= b; i--) #define forR(j,a, b) for (int j = a; j >= b; j--) #define inf 0x3f3f3f3f using namespace std; const int max2 = 100 + 10; const int max3 = 1000 + 10; const int max4 = 10000 + 10; const int max5 = 100000 + 10; const int max6 = 1000000 + 10; typedef long long ll; int sum[2 * max4]; //sum[x]代表编号位x的区间出现了多少个数 //对区间[0,n-1]进行初始化 void init(int nl,int nr,int root) //[nl,nr]代表当前区间,root代表当前区间的编号 { sum[root] = 0; //对这个区间初始化 if (nl == nr) return; const int mid = nl + (nr - nl) / 2; init(nl, mid, root << 1); //初始化左子树 init(mid + 1, nr, root << 1 | 1); //初始化右子树 } void upData(int id, int nl, int nr, int root) //id代表要修改的点的位置,等于是将数id标记成出现的状态,[nl,nr]代表当前区间,root代表当前区间编号 { if (nl == nr) sum[root] = 1; else { int mid = nl + ( (nr - nl) >> 1); if (id <= mid) upData(id, nl, mid, root << 1); else upData(id, mid + 1, nr, root << 1 | 1); sum[root] = sum[root << 1] + sum[root << 1 | 1]; } } int query(int fl,int fr,int nl,int nr,int root) //[fl,fr]代表要查询的区间,[nl,nr]代表当前区间,root代表当前区间的编号 { if (fl <= nl && nr <= fr) //当前区间落在了查询区间内,一般不会触发,主要是查询的范围比总区间还大的时候才会使用 return sum[root]; const int mid = nl + ((nr - nl) >> 1); int ans = 0; if (fl <= mid) //查询当前区间的左子树 ans += query(fl, fr, nl, mid, root << 1); if (fr > mid) //查询当前区间的右子树 ans += query(fl, fr, mid + 1, nr, root << 1 | 1); return ans; } int main() { int n; while(scanf("%d",&n)!=EOF) { init(0, n - 1, 1); //树的初始化 int value[5 * max3]; //value[i]记录第x位的数据大小 int sum = 0; //记录序列的组合总数 for(int i =0 ; i < n ; i++) { scanf("%d", value + i); sum += query(value[i], n - 1, 0, n - 1, 1); upData( value[i], 0, n - 1, 1); } int min_pair = sum; for(int i = 0 ; i < n ; i++) //n-1次将第一个数移动到最后一个 { sum = sum + (n - 1 - value[i]) - value[i]; //n-1-value[i]代表将第i位的数据放到最后一位的时候,让所有比value[i]大的数都可以形成一个配对 //对于value[i],因为原来的位置在最前面,所有比这个数小的数都可以和其形成一个组合 //相对于上一个序列,总和为增加新的配对数并减去最小的配对数 min_pair = min(min_pair, sum); } printf("%d\n", min_pair); } return 0; }