您的位置:首页 > 其它

HDU1394 Minimum Inversion Number(线段树单点更新,暴力,逆序数)

2017-04-30 20:50 429 查看
题目:


Minimum Inversion Number

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 19790    Accepted Submission(s): 11891


Problem 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

 

Author

CHEN, Gaoli

 

Source

ZOJ Monthly, January 2003

 

Recommend

Ignatius.L   |   We have carefully selected several similar problems for you:  1698 1540 1542 1255 1828 

 

Statistic | Submit | Discuss | Note
思路:

说实话。。这题的题意不好懂,所幸看到了大牛的博客,详细讲了题意,我就顺便把题意copy过来吧。。

弄了半天才弄懂题目的意思,就是求最小的逆序数,在此贴下逆序数的概念
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。
也是就说,对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。
 
题目的意思就好比给出一个序列
如:0 3 4 1 2
设逆序数初始n = 0;
由于0后面没有比它小的,n = 0
3后面有1,2 n = 2
4后面有1,2,n = 2+2 = 4;
所以该序列逆序数为 4
其根据题意移动产生的序列有
3 4 1 2 0   逆序数:8
4 1 2 0 3  逆序数:6
1 2 0 3 4  逆序数:2
2 0 3 4 1  逆序数:4
所以最小逆序数为2

在知道了题意以后,我们采用两种方法来进行这个题的求解。。。

①暴力法:直接第一遍for循环遍历一遍比他大的数,然后把剩下的数给递推出来,这个题有一个结论,如果求出第一种情况的逆序列,其他的可以通过递推来搞出来,一开始是t[1],t[2],t[3]....t
,它的逆序列个数是N个,如果把t[1]放到t
后面,逆序列个数会减少t[1]个,相应会增加N-(t[1]+1)个  .利用这个结论在把其他的情况递推出来,求最小值

②线段树:建立一棵空树,然后每一次查询询问的是当前有几个比当前这个数大的数,然后每次把这个点插入这一课线段树,最后就求出了第一个的逆序数,然后在用这个结论依次求出其他的,然后取最小值。

代码1(线段树):

#include <cstdio>
#include <cstring>
#include <cctype>
#include <string>
#include <set>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
#define inf 0x3f3f3f3f
#define mod 10000007
#define debug() puts("what the fuck!!!")
#define N 1000020
#define M 1000000
#define ll long long
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1 //位运算,|可以理解为+
int sum[4*N];
void pushup(int rt)//更新该节点维护的值,求和
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)//建立线段树
{
sum[rt]=0;
if(l==r) return;
int m=(l+r)>>1;
build(lson);
build(rson);
}
void update(int p,int l,int r,int rt)//单点更新
{
if(l==r)
{
sum[rt]++;
return;
}
int m=(l+r)>>1;
if(p<=m)
update(p,lson);
else
update(p,rson);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt)//要查询的区间和当前的左右节点和根节点
{
if(L<=l&&r<=R)//[l,r]∈[L,R]
{
return sum[rt];
}
int m=(l+r)>>1;//找中间点
int ret=0;
if(L<=m)  ret+=query(L,R,lson);
if(R>m)   ret+=query(L,R,rson);
return ret;
}
int x
;
int main()
{
int n;
while(~scanf("%d",&n))
{
build(0,n-1,1);//建立一棵空树
int cnt=0;
for(int i=0;i<n;i++)
{
scanf("%d",&x[i]);
cnt+=query(x[i],n-1,0,n-1,1);//返回比x[i]大的个数
update(x[i],0,n-1,1);//插入x[i]
}
int ret=cnt;
for(int i=0;i<n;i++)
{
cnt+=n-x[i]-x[i]-1;//递推式
ret=min(ret,cnt);
}
printf("%d\n",ret);
}
return 0;
}
代码2(暴力):

#include <cstdio>
#include <cstring>
#include <cctype>
#include <string>
#include <set>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
#define inf 0x3f3f3f3f
#define mod 10000007
#define debug() puts("what the fuck!!!")
#define N 1000020
#define M 1000000
#define ll long long
using namespace std;
int a[6000];
int main()
{
int n;
while(~scanf("%d",&n))
{
int ans=inf;
for(int i=0; i<n; i++)
scanf("%d",&a[i]);
int cnt=0;
for(int i=0; i<n; i++)
for(int j=i+1; j<n; j++)
{
if(a[i]>a[j])
cnt++;
}
if(ans>cnt)
ans=cnt;
for(int i=0; i<n; i++)
{
cnt=cnt-a[i]+n-1-a[i];
if(ans>cnt)
ans=cnt;
}
printf("%d\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: