Day7
离散化(Discretization)是算法竞赛中一个非常重要的技巧。它的核心思想是:当你在意的是数据之间的“相对大小(排名)”而不是“绝对数值”时,把大范围、稀疏的数据映射到小范围、紧凑的区间内。
以下从三个维度为你细讲:
1. 为什么要离散化?(痛点所在)
假设有一个问题:给你 \(n=10^5\) 个数,让你用树状数组统计一些信息。
如果这些数的大小范围是:
- 情况 A: 数值在 \(1\) 到 \(10^5\) 之间。直接开
int tree[100005],完美。 - 情况 B: 数值在 \(1\) 到 \(10^9\) 之间。你无法开出一个
int tree[1000000005]的数组,内存会直接炸掉(MLE)。 - 情况 C: 数值包含负数,如 \(-10^9\) 到 \(10^9\)。数组下标不能为负,树状数组直接瘫痪。
矛盾点: 树状数组(和线段树)的性能和空间是随数值范围(值域)增长的,而我们的内存是有限的。
突破口: 只有 \(10^5\) 个数。虽然数值很大,但如果把它们按从小到大排个名,第 1 小、第 2 小……第 \(10^5\) 小。这个“排名”的范围只有 \([1, 10^5]\)。
2. 离散化的核心逻辑
离散化就像是给这组数“发号码牌”。
例子:
原数组 \(A = [100, 5, 20000, 5, 999999]\)
- 去重排序:得到有序序列 \(L = [5, 100, 20000, 999999]\)
- 映射(发牌):
- 数值 \(5\) \(\to\) 排名 \(1\)
- 数值 \(100\) \(\to\) 排名 \(2\)
- 数值 \(20000\) \(\to\) 排名 \(3\)
- 数值 \(999999\) \(\to\) 排名 \(4\)
- 替换:原数组 \(A\) 变成了 \(A' = [2, 1, 3, 1, 4]\)
观察: \(A'\) 中数字的相对大小关系和 \(A\) 完全一致(比如 \(A[0] > A[1]\),对应的 \(A'[0] > A'[1]\) 依然成立)。现在我们只需要一个大小为 \(4\) 的树状数组就能处理原本需要 \(10^6\) 空间的逻辑。
3. 代码实现细节
在你提供的代码中:
for(int i=1;i<=n;i++) scanf("%lld",&a[i]), lsh[i] = a[i]; // 1. 复制一份到离散化数组
sort(lsh+1, lsh+n+1); // 2. 排序
// unique(lsh+1, lsh+n+1); // (可选) 去重
for(int i=1;i<=n;i++)
a[i] = lower_bound(lsh+1, lsh+n+1, a[i]) - lsh; // 3. 查找排名并替换
sort:确定谁大谁小。unique:如果你希望相同的数字对应同一个排名,就需要去重。在求逆序对时,如果不对相同元素去重,lower_bound也会让相同的数找到相同的排名(第一个出现的位置),逻辑上也是通的。lower_bound:这是在有序数组中找“排名”的最快方法(二分查找),复杂度 \(O(\log n)\)。
4. 什么时候需要离散化?
当你看到以下特征时,就要考虑离散化:
- 空间限制:数组下标需要用到题目给出的数值,但数值范围(如 \(10^9\))远大于数组能开的大小。
- 性质只关乎大小:题目只关心 \(a_i\) 是否大于 \(a_j\),或者关心 \(a_i\) 在所有数中的排名(如:逆序对、求第 \(K\) 大、统计比当前数小的数有多少个)。
- 静态数据:离散化通常针对初始就给定的数据。如果是动态增加全新的、范围极大的数值,离散化就比较难做,通常需要改用动态开点线段树或平衡树。
总结
离散化就是把“稀疏的大值域”压缩成“连续的紧凑值域”。它是为了在不改变元素相对关系的前提下,让数据能够适配那些“以数值为下标”的数据结构(如树状数组)。
include<bits/stdc++.h>
define lowbit(x) x & (-x)
define int long long
using namespace std;
const int maxn = 5e5 + 10;
int n,a[maxn],sum[maxn],lsh[maxn];
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
sum[i] += y;
return ;
}
int query(int x)
{
int ans = 0;
for(int i=x;i>=1;i-=lowbit(i))
ans += sum[i];
return ans;
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),lsh[i] = a[i];
sort(lsh+1,lsh+n+1);
//unique(lsh+1,lsh+n+1);//如有需要可判重
for(int i=1;i<=n;i++) a[i] = lower_bound(lsh+1,lsh+n+1,a[i])-lsh;
int ans = 0;
for(int i=1;i<=n;i++)
{
add(a[i],1);
ans += i-query(a[i]);
}
printf("%lld\n",ans);
return 0;
}根据我的代码改

浙公网安备 33010602011771号