——引入
首先就是什么时候要用离散化呢?
就是当你的数据很大比如说大到1e9甚至说更大,并且你的数据范围很小,1e5甚至更小,在这种情况下,最平均的结果也是1e9/1e5,也就是说每1万个区间长度之间,只会有一个数,这样实在太稀疏了,而且我们想要对后面的数进行调用也是完全不可能是,没有一个数组会允许你有一个1e9的下标,会直接爆数据的
这时就要用到我们今天这个简单,但是在算法竞赛中也是很实用的一个方法——离散化
离散化的定义
那么首先来说一下 ,何为离散化,离散化的本质是建立了一段数列到自然数之间的映射关系(value -> index),通过建立新索引,来缩小目标区间,使得可以进行一系列连续数组可以进行的操作比如二分,前缀和等…
摘录自海风大佬的博客:http://t.csdnimg.cn/2oT6H
单论定义的话,确实还是有些许抽象的,因此我在这里给出一个实例
1 87 345 999 999 1e9,对于这段序列,数据范围仅为6(只有6个数,但是其数据大小这跨度可真是大啊),那么我们就要去给这个序列去离散化一下 1->1 , 87->2 , 345->3 ,999->4,1e9->5;
这里同时说明一下,离散化是有两种离散化的,一种是保序去重离散化,另一种是保序不去重离散化
我们一般用的都是第一种离散化方法,我们来介绍一下什么是保序,保序说的是里面的大小顺序是不能变的,3小于5,那么离散化后的数据一定也是3的离散化也是要小于5的离散化的,那么什么是去重呢,那就是说,对于同一个数据,只会产生一种离散化, 比如说一开始的数据里面有两个123,那么这两个123 后面离散化对应的数据一定是相同的
那么这时候就会有同学提出疑问了,那这不就是用map可以实现的吗?map可以去实现不保序的,大部分时候那个顺序还是很重要的
因此,我们的离散化步骤为
1.排序(一般都会有固定的大小顺序去离散化,一般是从大到小排序)
2.去重
3.二分排序好的数组,去找到下标(离散化)
完成了离散化之后,后续可以进行一些前缀和,差分,二分什么的操作,同时离散化优化线段树也是一个重要的考点,这里就不展开说明了
离散化例题
P1904 天际线
题意:就是说每组输入三个数组,分别代表建筑的左边界下标,建筑的高度,建筑的右边界下标,,然后问你整个建筑群的轮廓是什么
思路:如果学过离散化和线段树的都会想到一个在2018年被国际算法组认可的算法—— 扫描线算法,然后去求矩形周长并即可
当然我自己也写了一个扫描线算法的博客,有兴趣可以看一下:http://t.csdnimg.cn/M8Ed7
但是很可惜有个大犇分享了一个提高+降成普及-的一个纯暴力的思路,然后这题就被降数据了
因此我们的思路也为,一开始假设建筑的标志符flag为0,如果碰到当前最高的位置和flag不相同的时候,就要输出这个位置的下标+这个位置的高度,然后我们在输入的时候将每个位置的的最高高度记录下来即可
#include<bits/stdc++.h>
using namespace std;
#define int long long
int H[100005];
int l,r,h;
signed main()
{
while(cin>>l>>h>>r)
{
for(int i=l;i<r;i++)
{
H[i]=max(H[i],h);
}
}
int flag=0;
for(int i=1;i<=10000;i++)
{
if(H[i]!=flag)
{
cout<<i<<" "<<H[i]<<" ";
flag=H[i];
}
}
return 0;
}
P1955 [NOI2015] 程序自动分析
题意:就是说有t组数据,每组数据输入n个操作,然后每次输入a,b还有flag,如果flag=1,那么就说明a==b。如果flag=0,就说明a!=b
思路:我们先处理操作为1的,因为操作为我们可以用到并查集的思路,现将操作为1的放到一个集合里面,然后如果再0的时候,这两个根节点相同的两个点是0的话,那就说明为NO,反之为YES
但是这题,输入的a和b有可能很大,所以需要去进行离散化一下
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
int n;
struct node{
int a;
int b;
int e;
}q[200005];
int num[400005];
int f[400005];
bool cmp(node a,node b)
{
return a.e>b.e;
}
int cha(int x)
{
if(f[x]==x)
return x;
return f[x]=cha(f[x]);
}
void bing(int a,int b)
{
f[cha(a)]=cha(b);
return ;
}
signed main()
{
cin>>t;
while(t--)
{
memset(q,0,sizeof(q));
memset(num,0,sizeof(num));
memset(f,0,sizeof(f));
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>q[i].a>>q[i].b>>q[i].e;
num[i]=q[i].a;
num[i+n]=q[i].b;
}
sort(num+1,num+1+2*n);
sort(q+1,q+1+n,cmp);
int len=unique(num+1,num+1+2*n)-(num+1);
for(int i=1;i<=n;i++)
{
q[i].a=lower_bound(num+1,num+1+len,q[i].a)-num;
q[i].b=lower_bound(num+1,num+1+len,q[i].b)-num;
}
int flag=1;
for(int i=1;i<=len;i++)
{
f[i]=i;
}
for(int i=1;i<=n;i++)
{
if(q[i].e==1)
{
bing(q[i].a,q[i].b);
}
else
{
if(cha(q[i].a)==cha(q[i].b))
{
flag=0;
break;
}
}
}
if(flag==1)
{
cout<<"YES\n";
}
else
{
cout<<"NO\n";
}
}
return 0;
}
P3029 [USACO11NOV] Cow Lineup S
题意:就是有给你n个牛的位置以及种类,然后让你判断最少多长的距离可以覆盖所有种类的牛
思路:我们发现,数据范围很大,因此,我们要对齐进行离散化,然后就会发现其实他和滑动窗口很像,因此用单调队列,双指针都可以解决,我用的双指针
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
struct node{
int x,id;
}a[50005];
map<int,int> mp;
map<int ,bool> vis;
int minn=0x3f3f3f3f;
bool cmp(node x,node y)
{
return x.x<y.x;
}
signed main()
{
cin>>n;
int cnt=0;//用于统计所有种类的个数
for(int i=1;i<=n;i++)
{
cin>>a[i].x>>a[i].id;
if(vis.find(a[i].id)==vis.end())//如果这种牛没出现过
{
vis[a[i].id]=1;
cnt++;
}
}
sort(a+1,a+1+n,cmp);
int l=1,r=1,num=1;
int flag=0;
if(num==cnt)
{
minn=a[1].x-1;
flag=1;
}
mp[a[1].id]++;
while(r<=n)
{
if(flag==0)
{
if(r==n)
{
break;
}
r++;
if(mp.find(a[r].id)==mp.end()||mp[a[r].id]==0)
{
num++;
}
mp[a[r].id]++;
if(num==cnt)
{
minn=min(minn,a[r].x-a[l].x);
flag=1;
}
}
else
{
l++;
if(mp[a[l-1].id]==1)
{
num--;
flag=0;
}
if(flag==1)
{
minn=min(minn,a[r].x-a[l].x);
}
mp[a[l-1].id]--;
}
}
cout<<minn;
return 0;
}