线段数 --- (单点更新、求逆序对)

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.

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;
}

线段数 --- (单点更新、求逆序对),布布扣,bubuko.com

时间: 2024-12-19 19:26:30

线段数 --- (单点更新、求逆序对)的相关文章

HDU 1394 Minimum Inversion Number (线段树 单点更新 求逆序数)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 题意:给你一个n个数的序列,其中组成的数只有0-n,我们可以进行这么一种操作:把第一个数移到最后一个,次数不限.问,在原始数列和最新生成的数列中逆序数最小可以是多少? 刚开始以为需要枚举求逆序数,但最后知道了这个题是有规律的:一个由0-n组成的n个数的数列,当第一个数移到最后一位的时候,整个数列的逆序数会减少x[i](移动前,后面比他小的),会增加n-x[i]-1(移动后,前面比他大的). 那

HDU 1754 I Hate It 线段树单点更新求最大值

题目链接 线段树入门题,线段树单点更新求最大值问题. #include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #define N 200005 using namespace std; int data[N]; struct Tree { int l,r,ans; }tree[N*4]; void build(in

数状数组求逆序对

逆序对在很多地方用的到.以前都是用归并排序或线段树求,在<mato的文件管理>看到有人用树状数组求,很简单!整理如下: 思路: 首先,开一个大小为这些数的最大值的数组,作为树状数组. 然后,将各个数按顺序依次加入该数组.方法为:这个数大小对应的它在线段树中的位置,对这个位置上的数加1,并更新树状数组.所以当前树状数组中存着所有原数字序列中当前数前面的数,而getsum(i)就是 i 前面小于等于 i 的数的个数.i-getsum(i)-1也就是大于它的个数.这就是逆序对了. 把每一个的逆序对数

HDU 2795 Billboard (线段树单点更新 &amp;&amp; 求区间最值位置)

题意 : 有一块 h * w 的公告板,现在往上面贴 n 张长恒为 1 宽为 wi 的公告,每次贴的地方都是尽量靠左靠上,问你每一张公告将被贴在1~h的哪一行?按照输入顺序给出. 分析 : 这道题说明了每一次贴都尽量选择靠上靠左的位置,那既然这样,我们以1~h建立线段树,给每一个叶子节点赋值初值 w 表示当前行最大能够容纳宽度为 w 的公告纸,那么对于某一输入 wi 只要在线段树的尽量靠左的区间找出能够容纳这张公告的位置(即叶子节点)然后减去 wi 即可,需要对query()函数进行一点改造,将

HDU 1754 I Hate It (线段树单点更新求最大值RMQ)

I Hate It Problem Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少.这让很多学生很反感. 不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问.当然,老师有时候需要更新某位同学的成绩. Input 本题目包含多组测试,请处理到文件结束.在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目.学生ID编号分别从1

(c) hdu1394* (求逆序对数)(线段树)

(c) hdu1394 如在阅读本文时遇到不懂的部分,请在评论区询问,或跳转 线段树总介绍 线段树求逆序对数比较少见啊(归并排序多快啊...但是本文是讲解线段树写法...),何况这题还加了点别的玩意儿... 1. 本来这种题目要离散化的,可是体中保证了数列0~n-1. 2. 每次把首位放到最末,显然不能 每次都求逆序对 ,于是又到了推 倒 导时间. 由 a1 a2 ... an 变为 a2 a3 ... an a1 减少的逆序对数为 a2~an中比a1小的数的个数 增加的逆序对数为 a2~a1中

hdu1394(线段树求逆序对)

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 线段树功能:update:单点增减 query:区间求和 分析:如果是0到n-1的排列,那么如果把第一个数放到最后,对于这个数列,逆序数是减少a[i],而增加n-1-a[i]的,所以每次变化为res+=n-a[i]-1-a[i]. #include<iostream> #include<cstdio> #include<cstring> #include<alg

hdoj 1394 Minimum Inversion Number【线段树求逆序对】

求逆序对有很多算法,这里说一下线段树求逆序对的思想. 知识点:线段树,逆序对,单点更新,成段求和 算法:线段树求逆序数的前提条件是要离散化,变成连续的点,首先建树,每个节点设置一个num值为0. 然后根据逆序对的定义,前面出现过的比当前数大的个数的和,我们需要求前面的比他大的数,其实就相当于从当前a[i]点对他后面所有出现过的数求和一次.然后把当前点的值在线段树叶子节点变为1,表示出现过,并向上更新到线段树里面.比如说样例4 2 1 5 3 首先4后面没有值,4更新为1,4--5区间更新为1,1

Minimum Inversion Number(线段树单点更新+逆序数)

Minimum Inversion Number(线段树单点更新+逆序数) Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Status Description The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy