离散化
如果有一些数组,元素个数不多,但是元素值的跨度很大。我们可以将它们的值替换成小一点的数,使它们的值变得紧凑,同时大小关系仍然与原数组一样。
比如有一个数组 a = { 1 , 1000000 , 100 , 30000 , 999999999 } a=\{1,1000000,100,30000,999999999\} a={1,1000000,100,30000,999999999},我们可以把它替换为 a ′ = { 1 , 4 , 2 , 3 , 5 } a'=\{1,4,2,3,5\} a′={1,4,2,3,5}
并记住对应关系。这样替换以后,很多问题就很好办了。
离散化这样操作:
设原数组为 a a a, 将原数组复制一份,设为数组 b b b,对 b b b数组排序,去重,然后对原数组 a a a的每个元素 a i a_i ai,在 b b b数组中查找 a i a_i ai, 用查找到的位置来代替 a i a_i ai.
sort(b + 1, b + n + 1);
int tot = unique(b + 1, b + n + 1) - (b + 1);
for(int i = 1; i <= n; i++){
a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
}
例1. 用树状数组求逆序对的个数
给一个数组 a a a,有n个元素,求数组中的逆序对的个数。
n ≤ 100000 , 1 ≤ a i ≤ 1 0 9 n\leq 100000,1 \leq a_i \leq 10^9 n≤100000,1≤ai≤109
分析
设树状数组为 t r e e tree tree。
将数组 a a a从左到右访问,访问到 a [ i ] a[i] a[i]时,
首先在 t r e e tree tree先执行 g e t s u m ( a [ i ] ) getsum(a[i]) getsum(a[i]),求出前缀和,这个前缀和就是在 a [ i ] a[i] a[i]左边小于 a [ i ] a[i] a[i]的元素个数。
然后再执行 a d d ( a [ i ] , 1 ) add(a[i], 1) add(a[i],1)。
t r e e tree tree数组要用 a [ i ] a[i] a[i]的值做下标,但 a [ i ] a[i] a[i]的值太多,必须离散化。
#include <bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define lowbit(a) (a & -(a))
#define LL long long int
int n, tot, num[MAXN], tmpnum[MAXN], rk[MAXN], tree[MAXN];
LL ans;
int getsum(int x){
int res = 0;
while(x){
res += tree[x];
x -= lowbit(x);
}
return res;
}
void update(int x){
while(x <= tot){
tree[x]++;
x += lowbit(x);
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &num[i]);
tmpnum[i] = num[i];
}
sort(num + 1, num + n + 1);
tot = unique(num + 1, num + n + 1) - num - 1; //unique为去重函数,返回去重后的数组的尾地址
//下面的rk数组即为离散化过后的数组
for(int i = 1; i <= n; i++)
rk[i] = lower_bound(num + 1, num + tot + 1, tmpnum[i]) - num;
for(int i = 1; i <= n; i++){
ans += (i - 1 - getsum(rk[i]));
update(rk[i]);
}
printf("%lld\n", ans);
return 0;
}
例2. parity
有一个01序列,长度≤1000000000,现在有n条信息,每条信息的形式是"a b even/odd", 表示序列的第a位到第b位元素之间的元素总和是偶数/奇数。 你的任务是对于这些给定的信息,输出第一个不正确的信息所在位置-1。信息的数目不超过5000。 如果信息全部正确,即可以找到一个满足要求的01序列,那么输出n。
分析
因为序列长度太大,而实际查询的次数最多只有5000,即位置数最多5000*2个,这就是fa数组元素个数了。
长度太大,所以需要离散化。
然后再用带权并查集处理即可。
如查询: a b e v e n / o d d a \; b \; even/odd abeven/odd, 即在 b → ( a − 1 ) b\to (a-1) b→(a−1)连一条权值为0/1的边.
如果当前查询的两个点之前已经联通了,则判断是否矛盾,如果矛盾,则该次查询就是假话。
#include <bits/stdc++.h>
using namespace std;
#define MAXN 5005
struct query{
int a, b;
bool flg;
}que[MAXN];
int n, m, rk[MAXN << 1], tmp[MAXN << 1];
int fa[MAXN << 1], w[MAXN << 1];
int a, b, ans;
char s[5];
int getroot(int x){
if(fa[x] == 0)return x;
int tmp = getroot(fa[x]);
w[x] ^= w[fa[x]];
return fa[x] = tmp;
}
void my_union(int a, int b, bool flg){
int ra = getroot(a), rb = getroot(b);
if(ra != rb){
fa[ra] = rb;
w[ra] = (w[a] ^ w[b] ^ flg);
}
}
int main(){
scanf("%d %d", &m, &n);
for(int i = 1; i <= n; i++){
scanf("%d %d %s", &que[i].a, &que[i].b, s);
if(s[0] == 'o') que[i].flg = 1;
else que[i].flg = 0;
tmp[i * 2 - 1] = --que[i].a;
tmp[i * 2] = que[i].b ;
}
int tot = n << 1;
sort(tmp + 1, tmp + tot + 1);
tot = unique(tmp + 1, tmp + tot + 1) - (tmp + 1);
for(int i = 1; i <= n; i++){
que[i].a = lower_bound(tmp + 1, tmp + tot + 1, que[i].a) - tmp;
que[i].b = lower_bound(tmp + 1, tmp + tot + 1, que[i].b) - tmp;
}
for(int i = 1; i <= n; i++){
int ta = getroot(que[i].a), tb = getroot(que[i].b);
if(ta == tb){
if((w[que[i].a] ^ w[que[i].b]) != que[i].flg){
printf("%d\n", i - 1);
return 0;
}
}
else
my_union(que[i].a, que[i].b, que[i].flg);
}
printf("%d\n", n);
return 0;
}