离散化
离散化就是将值域较大的数升序映射到值域较小的数上,即把数组中所有不重复的数升序映射到他的下标上
比如将1 700 1000 1000000 100000000分别映射到1,2,3,4上
离散化步骤:
1.a中可能会有重复元素,先去重
2.用二分找a里每个值离散化之后的数是多少
一维离散化
比如将大小为n的vector类型的a数组中的数离散化:
vector<int> a;//所有待离散化的值
sort(a.begin() ,a.end() );//先排序
a.erase(unique(a.begin() ,a.end() ),a.end() ); //unique函数作用:将升序数组a去重,将a中重复的数放在数组的后面,返回重复的数的第一个位置。erase函数作用:删除首地址和末地址中的数
找出x离散化后的数:
int find(int x){
return lower_bound(a.begin(),a.end(),x)-a.begin+1;//返回以1开头的离散化后的数
}
二维离散化
对于n*m的图:
先将所有横坐标加入x数组,排序去重
然后再扫一遍x数组,与一维离散化不同的是,如果当前坐标不是前一个坐标+1(即两个坐标不相邻),那么当前坐标离散化的数值就得是前一个离散化之后的数值+1,即两个坐标在新图中相隔了一列
比如(3,300) (200,800) (201,600)这三个点 x:3 4 200 3->1,200和3不相邻,所以200->3,201和200相邻,201->4
y:300 600 800
300->1,600->3,800->5
所以上面三个点离散化之后是:(1,1),(3,5),(4,3)
如果想看新图中每个块在老图中占多少个块,那么我们就用nx[i]来记录新图中横坐标为i的点在老图中占几行,ny[i]来记录新图中纵坐标为i的点在老图中占几列,那么对于新图的一个点(i,j),他在老图中占的块数就是nx[i]*ny[j]
详细步骤如下:
对于横坐标:
横坐标用x数组来进行离散化
将旧图映射为下标都从0开始的新图
先将0和n加入x数组
再将所有的点的横坐标都加入x数组
将x数组排序去重
nx数组来存新图每个点对应的老图的间隔(算联通块用)
mpx数组来存旧图中每个点对应的新图的坐标
从第二个元素,即i=1来扫描x数组:
当x[i]!=x[i-1]+1时,说明i和i-1在新图中相隔了一行,并且这一行在老图中的间隔是x[i]-x[i-1]-1,那么nx中就加一个元素x[i]-x[i-1],然后再加上x[i]占的间隔1,那么mpx[x[i]]就是当前nx数组的大小-1(因为映射从下标为0开始)
//dfs遍历每个连通块求在老图中的大小
void dfs(int i,int j,int &con){
vis[i][j]=true;
con+=nx[i]*ny[j];
for(int kk=0;kk<4;kk++){
int xx=i+dx[kk];
int yy=j+dy[kk];
if(xx<0||xx>=nx.size() ||yy<0||yy>=ny.size() )continue;
if(vis[xx][yy])continue;
dfs(xx,yy,con);
}
}
//先往x和y中分别加入(0,n)和(0,m)
x.push_back(0);
x.push_back(n);
y.push_back(0);
y.push_back(m);
//将所有点的横纵坐标都加入相应的x和y数组中
for(int i=1;i<=k;i++){
x.push_back(q[i].x);
y.push_back(q[i].y );
}
//离散化x和y
sort(x.begin() ,x.end() );
sort(y.begin() ,y.end() );
x.erase(unique(x.begin() ,x.end() ),x.end() );
y.erase(unique(y.begin() ,y.end() ),y.end() );
//分别遍历x和y,算出nx和mpx
int numx=x.size() ;
int numy=y.size() ;
for(int i=1;i<numx;i++){
if(x[i]!=x[i-1]+1) nx.push_back(x[i]-x[i-1]-1);
nx.push_back(1);
mpx[x[i]]=nx.size() -1;
}
for(int i=1;i<numy;i++){
if(y[i]!=y[i-1]+1) ny.push_back(y[i]-y[i-1]-1);
ny.push_back(1);
mpy[y[i]]=ny.size() -1;
}
//将原图中被占用的点在新图中标记
for(int i=1;i<=k;i++){
int xx=q[i].x ;
int yy=q[i].y ;
vis[mpx[xx]][mpy[yy]]=true;
}
//在新图中求联通块的个数和每个联通块的大小,ans存每个联通块在老图中的格子个数
for(int i=0;i<nx.size() ;i++){
for(int j=0;j<ny.size() ;j++){
int sum=0;
if(!vis[i][j]){
dfs(i,j,sum);
if(sum>0){
ans.push_back(sum);
}
}
}
}
例题
HDU 5925 Coconuts
题意:
有一个横纵坐标范围是[1,1e9]的网格图,但是图上只有k个点(k<=200),求没有被点覆盖的网格能组成几个连通块,输出联通块的个数,并且按照连通块的个数从小到大的顺序输出每个连通块的个数
思路:
建成新图之后对于每个没有走过的点dfs一遍求块数就好了
#include<bits/stdc++.h>
#include<vector>
#include<map>
#define int long long
using namespace std;
const int N=500+10;
int n,m;
int k;
struct name{
int x,y;
}q[N];
vector<int> x,y;
vector<int> nx,ny,ans;
map<int,int>mpx,mpy;
bool vis[N][N];
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
void dfs(int i,int j,int &con){
vis[i][j]=true;
con+=nx[i]*ny[j];
for(int kk=0;kk<4;kk++){
int xx=i+dx[kk];
int yy=j+dy[kk];
if(xx<0||xx>=nx.size() ||yy<0||yy>=ny.size() )continue;
if(vis[xx][yy])continue;
dfs(xx,yy,con);
}
}
void sove(){
cin>>n>>m;
cin>>k;
memset(vis,false,sizeof vis);
x.clear();
y.clear() ;
mpx.clear();
mpy.clear();
ans.clear() ;
nx.clear() ;
ny.clear() ;
x.push_back(0);
x.push_back(n);
y.push_back(0);
y.push_back(m);
for(int i=1;i<=k;i++){
cin>>q[i].x >>q[i].y ;
x.push_back(q[i].x);
y.push_back(q[i].y );
}
sort(x.begin() ,x.end() );
sort(y.begin() ,y.end() );
x.erase(unique(x.begin() ,x.end() ),x.end() );
y.erase(unique(y.begin() ,y.end() ),y.end() );
int numx=x.size() ;
int numy=y.size() ;
for(int i=1;i<numx;i++){
if(x[i]!=x[i-1]+1) nx.push_back(x[i]-x[i-1]-1);
nx.push_back(1);
mpx[x[i]]=nx.size() -1;
}
for(int i=1;i<numy;i++){
if(y[i]!=y[i-1]+1) ny.push_back(y[i]-y[i-1]-1);
ny.push_back(1);
mpy[y[i]]=ny.size() -1;
}
for(int i=1;i<=k;i++){
int xx=q[i].x ;
int yy=q[i].y ;
vis[mpx[xx]][mpy[yy]]=true;
}
for(int i=0;i<nx.size() ;i++){
for(int j=0;j<ny.size() ;j++){
int sum=0;
if(!vis[i][j]){
dfs(i,j,sum);
if(sum>0){
ans.push_back(sum);
}
}
}
}
sort(ans.begin() ,ans.end() );
cout<<ans.size() <<endl;
for(int i=0;i<ans.size() ;i++){
if(i==0)cout<<ans[i];
else cout<<" "<<ans[i];
}
cout<<endl;
}
signed main(){
int tt;
cin>>tt;
for(int ca=1;ca<=tt;ca++){
printf("Case #%lld:\n",ca);
sove();
}
return 0;
}