开篇
这是很久之前做过的题了,也是我第一次接触离散化的题。之前一直忘了记下来,今天补写一篇离散化博客。
题目
原链接:https://www.luogu.com.cn/problem/P1955
(懒得复制粘贴再改格式了,大家直接看吧)
解说
最开始看过一遍题这东西简单的一批啊,并查集板子吧。先处理相等关系,相等的扔到一个集子里,然后判断不等关系,每个不等关系中的两个数要是在一个集子里就不成立,一遍判断下来啥事没有就成立。又能水过一道题了哈哈哈!
开始一写,笑容渐渐凝固在脸上:等一下这个数的大小小于等于1e9,我的father数组开不下啊[托腮][托腮]。最开始还不会离散化,想的是开几个int数组,用要存的数%每个数组的大小,分开储存。比如开10个1e8的数组,100000005就存在第二个数组第五个,10就存在第一个数组第十个这样的。后来一想出题人肯定不会是想让我们这样的,极大的可能会卡个内存啥的,这种方法实际上的内存是没有改变的,所以估计会被卡掉,而且极其麻烦。
再仔细看一看,题目中n<=1e6,描述的每个关系涉及两个数,那么最多就涉及2000000个数,那么就相当于1e9的储存空间中有大部分是空着的,简直是在浪费空间,能优化吗?显然可以,在这个题中,我们需要的只是区分每一个数,而不需要每个数的具体大小,那么我们是不是就可以把一些大一点的数用小一点的数代替呢?就像暗号一样,反正最后只要保证每个数有专属于它的暗号,可以区分就可以了。这就是离散化。
那么具体该怎么做呢?毕竟之前没接触过离散化所以此时我在网上一顿搜索应该情有可原
那么,离散化一共包括三部。一、排序。二、去重。三、映射。下面一步一步讲解。
排序
这是非常简单的一步。一边读入一边把所有涉及到的数字存在all数组里,然后sort排序就行了(P.S. 可以顺便在这里特判一下一个不等关系都没有就直接YES然后continue)。
1 cin>>n; 2 memset(all,0,sizeof(all)); 3 int judge=0; 4 for (int i=1;i<=n;i++){ 5 cin>>cun[i].a>>cun[i].b>>cun[i].inf; 6 if(!cun[i].inf) judge=1; 7 all[tot]=cun[i].a; tot++; 8 all[tot]=cun[i].b; tot++; 9 } 10 if(!judge){cout<<"YES"<<endl;continue;} 11 sort(all+1,all+tot);
去重及映射
这就是之前不会的原因了,这部分涉及两个之前没接触过的函数lower_bound()和unique()(都属于algorithm库)。unique的作用是“去掉”容器中重复元素(不一定要求数组有序),它会把重复的元素添加到容器末尾(所以数组大小并没有改变),而返回值是去重之后的尾地址(因此我们可以相当于地认为数组大小变成了unique(all+1,all+tot)-all-1,后面的元素就都相当于被抹掉了,那么我们就得到了一个本题涉及的所有数的列表,这样的话我们就可以把一个数的下标直接当成它的“暗号”
),而函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置(注意它返回的也是位置),通过lower_bound()我们就可快速找到一个数在all数组中的位置,然后储存起来它的下标了。
1 int cnt=unique(all+1,all+tot)-all-1; 2 for (int i=1;i<=n;i++){ 3 cun[i].a=lower_bound(all+1,all+cnt+1,cun[i].a)-all; 4 cun[i].b=lower_bound(all+1,all+cnt+1,cun[i].b)-all; 5 }
最后
最后就直接跑并查集就行了。我认为这就不用再说了吧。
完整代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 int all[2000002],fa[20000002]; 4 struct u{ 5 int a,b,inf; 6 }cun[20000002]; 7 bool cmp(u a,u b){ 8 return a.inf>b.inf; 9 } 10 int find(int x){ 11 if(fa[x]==x) return x; 12 return fa[x]=find(fa[x]); 13 } 14 int main(){ 15 ios::sync_with_stdio(false); 16 int t; 17 cin>>t; 18 while(t--){ 19 int n,tot=1; 20 cin>>n; 21 memset(all,0,sizeof(all)); 22 int judge=0; 23 for (int i=1;i<=n;i++){ 24 cin>>cun[i].a>>cun[i].b>>cun[i].inf; 25 if(!cun[i].inf) judge=1; 26 all[tot]=cun[i].a; tot++; 27 all[tot]=cun[i].b; tot++; 28 } 29 if(!judge){cout<<"YES"<<endl;continue;} 30 sort(all+1,all+tot); 31 int cnt=unique(all+1,all+tot)-all-1; 32 for (int i=1;i<=n;i++){ 33 cun[i].a=lower_bound(all+1,all+cnt+1,cun[i].a)-all; 34 cun[i].b=lower_bound(all+1,all+cnt+1,cun[i].b)-all; 35 } 36 for (int i=1;i<=cnt+1;i++) fa[i]=i; 37 int js=0; 38 sort(cun+1,cun+1+n,cmp); 39 while(1){ 40 js++; 41 if(!cun[js].inf) break; 42 int xx=find(cun[js].a),yy=find(cun[js].b); 43 if(xx!=yy) fa[xx]=yy; 44 } 45 int ans=1; 46 for (int i=js;i<=n;i++){ 47 int xx=find(cun[i].a),yy=find(cun[i].b); 48 /*cout<<xx<<' '<<yy<<endl;*/ 49 if(xx==yy){ 50 ans=0; 51 break; 52 } 53 } 54 if(ans) cout<<"YES"<<endl; 55 else cout<<"NO"<<endl; 56 } 57 return 0; 58 }
结语
这大概是我自学的最好的一会了。
unique()和lower_bound()都彻彻底底地查了用法,离散化可以说学清楚了。或许,这就是所谓的“超越教练员”必经的一步吧。
幸甚至哉,歌以咏志。