离散化模板
目录
必要知识基础
unique函数(C++):(15条消息) C++中的unique函数_simao-CSDN博客_c++ unique()
lower_bound和upper_bound的用法(C++):(15条消息) lower_bound和upper_bound的用法(C++)_理想不是梦33的博客-CSDN博客
引入
离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。这是百度百科上的定义。那么举个栗子,某个题目告诉你有1e5个数,每个数大小不超过1e9,要你对这些数进行操作(比如并查集之类的)。那么肯定不能直接开1e9大小的数组,但是1e5的范围就完全没问题。在举个栗子,现在对{4,7,6,9}进行离散化,那么得到的结果是{1,3,2,4},也就是说,当我们并不需要这些数据具体是多少时,就只需要知道他们的相对大小就行了。
模板一(有重复元素,时间复杂度较高)
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+7;
int t[N],a[N],n,m;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],t[i]=a[i];
sort(t+1,t+n+1); //离散化之前别忘记排序
//不相同元素个数
m=unique(t+1,t+n+1)-t-1;/*因为这里多包涵了t[0]的地址,
实际上数组地址(*t+0)这里是没有存放元素的,所以要减去(t+1) */
cout << "m:" << m << endl;
for(int i=1;i<=n;i++)
a[i]=lower_bound(t+1,t+1+m,a[i])-t;
//离散化之后的结果
for(int i=1;i<=n;i++)
cout << a[i] << " ";
}
在这段代码中,a[]经过离散,范围就变成了m。解释一下,unique是c++自带的一个函数,表示对一个数列去重,它指向的是去重后容器中不重复序列的最后一个元素的下一个元素的地址。所以在后面要减去首地址。那么这种离散化对于有重复元素的数列也可以适用,但复杂度相对后面要讲的第二种方法会高些。
举个栗子
{6,8,4,9,5,6,7,4},首先排序后得到{4,4,5,6,6,7,8,9},去重{4,5,6,7,8,9},然后原序列就变成了{3,5,1,6,2,3,4,1}。
怪怪的写法
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+7;
int t[N],a[N],n,m;
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i],t[i]=a[i];
sort(t,t+n);
//不相同元素个数
m=unique(t,t+n)-t;//数组从t[0]开始,所以这里不用再减去1
cout << "m:" << m << endl;
for(int i=0;i<n;i++)
a[i]=lower_bound(t,t+m,a[i])-t;
//离散化之后的结果
for(int i=0;i<n;i++)
cout << a[i] << " ";
}
模板二(复杂度比上面那一种要优,但不能处理重复元素)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5+7;
int order[N];
struct Node
{
int v,id;
bool operator < (const Node a)const{
return v < a.v;
}//排序用
}a[N];
int main()
{
int n;
cin >> n;
for(int i=1;i<=n;i++)
{
cin >> a[i].v;
a[i].id = i;
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
order[a[i].id] = i;
for(int i=1;i<=n;i++)
cout << order[i] << " ";
cout << endl;
}
这种方法直接用结构体存储原本的数列的元素的位置,然后排序以后将他们再重新赋值。那么rank[]就是结构体a[]离散化后的结果。
举个栗子:
v: 3 6 5 10 8
id:1 2 3 4 5
排序以后:
v: 3 5 6 8 10
id:1 3 2 5 4
所以离散化以后:
v: 3 5 6 8 10
id:1 3 2 5 4
rk:1 2 3 4 5
在按原来的顺序排列:
v: 3 6 5 10 8
rk:1 3 2 5 4
总得来说离散化有三步走战略:
1.去重(可以用到unique去重函数)
2.排序
3.二分索引(可以用到lower_bound函数)
AC代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<stdlib.h>
#define maxn 1000007
#define ll long long
using namespace std;
int pre[maxn],book[maxn];
void init(int n)
{
for(int i=1;i<=n;i++) //init要从0开始,不然会有数据错误
pre[i] = i;
}
int find(int x)
{
if(pre[x] == x)
return x;
else
return pre[x] = find(pre[x]);
}
void unite(int x,int y)
{
int fx = find(x);
int fy = find(y);
if(fx != fy)
pre[fx] = fy;
}
struct node
{
int l,r,v;
}a[maxn];
bool cmp(const node &a,const node &b)
{
return a.v > b.v;
}
int main()
{
int t,n,cut,tmp,flag;
scanf("%d",&t);
while(t--)
{
//初始化
scanf("%d",&n);
cut = 0;
flag = 1;
memset(a,0,sizeof(a));
memset(book,0,sizeof(book));
init(maxn);
//离散化
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].v);
book[++cut] = a[i].l;
book[++cut] = a[i].r;
}
sort(book+1,book+cut+1);
tmp = unique(book+1,book+cut+1) - book - 1;
for(int i=1;i<=n;i++)
{
a[i].l = lower_bound(book+1,book+tmp+1,a[i].l) - book;
a[i].r = lower_bound(book+1,book+tmp+1,a[i].r) - book;
}
//排序,先联合,后判断
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
if(a[i].v)
unite(a[i].l,a[i].r);
else
{
if(find(a[i].l) == find(a[i].r))
{
flag = 0;
printf("NO\n");
break;
}
}
}
if(flag)
printf("YES\n");
}
return 0;
}
在上述代码中,并查集上级的初始化函数init()中,如果i从1开始会有数据错误,因为代码中离散化数组是从0开始的,所以可能会出现pre[0],而在pre的初始化中没有定义pre[0],因此数据出错,解决方法有:
(1)在主函数初始化模块中memset(pre,0,sizeof(pre))
(2)离散化数组book[]和上级数据pre[]从同一个值(0或者1)开始
例题:离散化+前缀和
样例模拟
AC代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+7;
int t[N],a[N],n,m;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],t[i]=a[i];
sort(t+1,t+n+1); //离散化之前别忘记排序
//不相同元素个数
m=unique(t+1,t+n+1)-t-1;/*因为这里多包涵了t[0]的地址,
实际上数组地址(*t+0)这里是没有存放元素的,所以要减去(t+1) */
cout << "m:" << m << endl;
for(int i=1;i<=n;i++)
a[i]=lower_bound(t+1,t+1+m,a[i])-t;
//离散化之后的结果
for(int i=1;i<=n;i++)
cout << a[i] << " ";
}