CF#540(Div.2)E - Infinite Inversions 离散化+树状数组(艰辛啃题)

原题目地址:传送门
参考博客地址:

  1. XDU_Skyline的CSDN博客-#301 (div.2) E. Infinite Inversions
  2. 树状数组求逆序对个数

听闻这题需要数据结构,结果啃这道题真的十分自闭。可以说现在还没有掌握这道题(可能是我找的那篇题解不是特明白或者思路比较清奇吧),不过在啃题解的过程中也有不少收获。

1.离散化

按照百度百科的说法:

离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。

大概就是在不改变原来数据相对关系的前提下,缩小数据绝对规模以达到对程序时空复杂度的节省。
代码中,先定义经过离散化后的数组:

int x[N];//x数组保存所有要交换的位置
int a[N/2], b[N / 2], rk[N];//rk[i]表示第i个位置的下标

之后在输入需要修改的数据时在线进行离散化处理:

for (int i = 1; i <= n; i++)
{
	scanf("%d%d", &a[i], &b[i]);
	x[i] = a[i]; x[i + n] = b[i];
	rk[i] = i; rk[i + n] = i + n;
}
sort(x + 1, x + 2 * n + 1);//将这些位置从小到大排序

程序把n个交换的元素对用2n的空间存放,前n空间存放交换对中的第一个值,后n空间存放交换对中的第二个值,经过处理后,所有被修改的数据都被安排的明明白白。

2.有序数组的去重

这项其实实现起来并不是难事,而且实现方法多种多样,原博主的代码中的去重方法的确很简洁

x[0] = 0;
int cnt = 0;
for (int i = 1; i <= 2 * n;i++)
if (i == 1 || x[i] != x[i - 1])
	x[++cnt] = x[i];

遍历数组,如果与前边t不同便重新记录,覆盖操作不会对去重产生影响。最后保留下的元素个数为cnt个。

3.关于题目

该题目是计算逆序对个数,数组无限长,但是真正会计数到的范围上限受被操作数据最大值影响。
在计数逆序对时,可以记录每个被swap的数x[i]后面有多少个比它小的数,那些比它小的数有两种。

  1. x[i]后面的被操作过的数。
  2. x[i]后面的没有被操作过的数。

对于第一类逆序对

只需要单纯求已知序列的逆序对个数,经过离散化之后,可以按照参考博客2方法进行求解。

对于第二类逆序对,需要先把sum函数问题解决:

for (int i = 1; i <= cnt; i++)
	sum[i] = sum[i - 1] + x[i] - x[i - 1] - 1;
	//sum[i]表示对于(0,i)之间的整数,小于x[i]且没有被swap过的元素的个数

显而易见,经过排序后,x数组中相邻元素之间的整数就是没有操作过的元素,所以上式中有"x[i]-x[i-1]-1",再加上sum[i-1]便是(0,i)中小于x[i]且没有被swap过的的元素的个数

之后开始处理第二类逆序对个数:

那么当一个数i位置从a被swap到b时,多出(或减少)的第二类逆序对个数应该是
sum[a]-sum[b]

综合以上,可以按照树状数组前缀和写出get()函数,再按照第二类逆序对个数求法,定义solve()函数,最后求解

for (int i = cnt; i; i--)
{
	ans += get(rk[i]);//第一部分:计算区间[i+1,n)中,有多少个元素的值小于rk[i],只考虑位置发生过改变的元素
	ans += solve(i, rk[i]);//第二部分:计算区间(i,rk[i])中,有多少个元素的值小于rk[i],只考虑位置未发生过改变的元素
	add(rk[i], 1);//标记rk[i]
}

原博主代码上rk数组虽然很关键,但还是思路有点儿迷,慢慢啃吧...

posted @ 2018-09-30 03:17  FinFin  阅读(208)  评论(0)    收藏  举报