CF#540(Div.2)E - Infinite Inversions 离散化+树状数组(艰辛啃题)
原题目地址:传送门
参考博客地址:
听闻这题需要数据结构,结果啃这道题真的十分自闭。可以说现在还没有掌握这道题(可能是我找的那篇题解不是特明白或者思路比较清奇吧),不过在啃题解的过程中也有不少收获。
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]后面有多少个比它小的数,那些比它小的数有两种。
- x[i]后面的被操作过的数。
- 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数组虽然很关键,但还是思路有点儿迷,慢慢啃吧...

浙公网安备 33010602011771号