• 【a703】求逆序对(树状数组的解法)


    Time Limit: 10 second
    Memory Limit: 2 MB

    问题描述
    给定一个序列a1,a2...an。如果存在i小于j 并且ai大于aj,那么我们称之为逆序对,求给定序列中逆序对的数目

    Input

    第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数。

    Output

    所有逆序对的总数


    Sample Input

    4
    3
    2
    3
    2
    

    Sample Output

    3

    【题解】

    这题的n最大值为10w.

    求逆序对的方法除了利用归并排序之外,还可以用树状数组来解决。

    以下是方法。

    比如

    3 2 8 5

    将它们排序(从大到小)

    8 5 3 2

    先把8放进去(原来的位置是3)

    但是在放之前,先检查位置3之前有没有其他数字放进去了(如果放进去了肯定是比8大的数字,但是它们的下标又小于3(逆序对!));

    因为没有

    所以就把下标3对应的树状数组改为1;

    即0 0 1 0

    然后是第大的5(原来的位置是4)

    则看看4前面有多少个元素已经放进去了(前缀和!)。

    发现有1个。则答案递增1;

    然后把tree_arr[4] 改为1

    即0 0 1 1

    然后是第三大的数字3,它原来的位置是1,但是1前面没有数字已经放进去。则不递增答案。

    最后是最小的元素2,它原来的位置就是2,然后位置2之前有一个数字3已经放在了位置1.即下标1的前缀和为1.则答案递增1.

    最后答案为2;

    而树状数组就是专门处理前缀和的。

    排序完毕之后,从大到小,从他们各自原来的位置,一直往左累加长条(前缀和)就可以了。然后累加完毕之后,需要把它放在原来的位置(置为1),则又要往右更新了。

    但是要注意一个问题。就是出现相同数字的情况。

    则我们在写比较函数的时候,让相同的数字,之前的位置大的放在后面。然后我们处理到连续的相同数字的时候。就记录这是连续相同数字里面的第i个。

    在累加完前缀和之后答案减去i-1,因为相同大小就不算逆序对了;

    【代码】

    #include <cstdio>
    #include <algorithm>
    
    struct data2
    {
    	int d, pos;
    };
    
    int n;
    __int64 tree_arr[100001] = { 0 };
    __int64 ans = 0;
    data2 a[100001];
    
    int lowbit(int x) //求整数x的二进制的最靠近右边的数字1所代表的数字。
    {
    	return x & -x; //-x是在x的二进制按位取反之后再加上1的结果。然后和x取与运算。
    }
    
    int cmp(const data2 &a, const data2 &b)//比较函数。
    {
    	if (a.d > b.d)//按数字从大到小排序。
    		return 1;
    	if (a.d == b.d && a.pos < b.pos)//如果数字的大小相同,则靠后的在后面。
    		return 1;
    	return 0;
    }
    
    void find(int now) //一直往左累加长条(区间和)
    {
    	if (now <= 0)//越界了则退出
    		return;
    	ans += tree_arr[now];//累加这一段的区间和
    	find(now - lowbit(now));//一直往左
    }
    
    void add(int now)//把数组中now位置上的数字递增1
    {
    	if (now > n)
    		return;
    	tree_arr[now] ++;
    	add(now + lowbit(now));//然后继续往左,去递增那些会受到影响的区间。
    }
    
    int main()
    {
    	//freopen("F:\rush.txt", "r", stdin);
    	//freopen("F:\rush_out.txt", "w", stdout);
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++)
    	{
    		scanf("%d", &a[i].d); //输入n个数据,并且记录它们原来的位置。
    		a[i].pos = i;
    	}
    	std::sort(a + 1, a + 1 + n, cmp);//把a数组以数字大小为关键字从大到小排序。
    	int now = 0;
    	for (int i = 1; i <= n; i++)//把每一个数字放到树状数组中。
    	{
    		if (i != 1 && a[i - 1].d == a[i].d)//如果出现了连续相同的数字就记录这是连续相同数字里的第x个,now=x-1;
    			now++;
    		else
    			now = 0;
    		find(a[i].pos);//往左累加前缀和
    		ans -= now;//减去重复的
    		add(a[i].pos);//修改与其相关的区间。
    	}
    	printf("%I64d", ans);
    	return 0;
    }


  • 相关阅读:
    修改ZXing,使之支持条形码识别
    varchar2和varchar的区别
    “PPT Controller” 项目进度
    如何发布打包并发布自己的Android应用(APP)
    C# 新浪微博滚动抓取 WeiboGrab
    小端法与大端法(MD5疑惑解1)
    MD5的实现
    struts标签if应用
    Hibernate annotation 自增主键 与 could not initialize proxy no Session
    新的征程
  • 原文地址:https://www.cnblogs.com/AWCXV/p/7632276.html
Copyright © 2020-2023  润新知