经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
c++ set 容器详解 附例题题解
来源:cnblogs  作者:DrRatio  时间:2024/5/13 8:55:38  对本文有异议
声明

本文中题解部分内容一部分转载自 @sonnety这篇博客中,本文为为方便复习而写的结论类文章,读者可自行跳转至原文处阅读。
image


PART 1 set

什么是 set

image
——来源cppreference

简言之,它就是一种存进去就可以自动按升序排列的特殊容器,通常的 set 还具有自动去重的功能。

定义方式:

  1. std::set<int>s;
  2. 'set'+<存储数据类型>+容器名

注意的是,这里存储数据类型不仅包含常用的intlong longdouble等易想到的,也可以是charstring这些非数值类的,排序标准为字典序(强者处处是惊喜)

怎么使用 set

首先,我们要了解几个 set 的常用方法:

  1. 指针类
  2. s.begin()//返回 s 的起始迭代器
  3. s.end()//返回 s 的超尾迭代器
  4. /*
  5. update:通用性更高的两个推荐函数
  6. s.begin()——>std::begin(s)
  7. s.end()——>std::end(s)
  8. */
  9. 容量类
  10. s.empty()//检查容器是否为空
  11. s.size()//返回元素数(返回值类型为 std::size_t 通常情况下是 unsigned long long )
  12. 修改类
  13. s.insert(w)//向容器中插入 w 这个元素
  14. s.erase(w)//在容器中删除 w 这个元素
  15. //参数为迭代器,并返回新的迭代器
  16. s.clear()//将 s 清空
  17. swap(s1,s2)//交换 s1 与 s2 两个容器内的元素
  18. 查找类
  19. s.find(w)//在容器中查找 w 这个元素所在的迭代器
  20. //若不存在该元素,则返回 s.end()
  21. s.lower_bound(w)//在容器中寻找第一个不小于 w 的元素迭代器
  22. //若不存在则返回 s.end()

我们可以观察到,很多 set 的返回值类型都是迭代器,因此还存在一个迭代器set<int>::iterator,它是一个指向 set<int> 中元素的仿指针,可以通过迭代器访问 set<int> 中的元素,并能够进行迭代器运算,如自增等操作。

注:迭代器的功能比指针多得多,并且安全性也更高。

我们在调用 s 中的值的时候,可以如下操作:

  1. 输出 s 中的所有值
  2. set<int>::iterator it;
  3. for(it=s.begin();it!=s.end();it++)
  4. cout<<*it<<' ';// *it 是获取当前迭代器指向的元素的值

自 c++11 引入了类型 auto 以及 range-for 语法后,我们可以更加简便地完成上面的操作。

  1. for(auto it:s)
  2. cout<<it<<' ';//这里 auto 的类型等同于我们定义 s 时的数据类型,也就是 int
  3. 所以它等同于:
  4. for(int it:s)
  5. cout<<it<<' ';

set 和 multiset

multiset 在 cppreference 中定义如下:

image

简言之, multiset 的特性有:

  1. 可以保留重复的元素,也就是没有自动去重的性质。
  2. multiset 支持插入、删除和查找操作的平均时间复杂度均为 \(O(log\,n)\),而 set 只支持插入和查找操作的平均时间复杂度为 \(O(log\,n)\),删除操作的平均时间复杂度为 \(O(1)\)
  3. multiset 在删除时只会删除元素值相同的元素中的一个,而不是全部删除或删除一些。

通常情况下,我们多使用 set ,因为它在进行查找等操作时更快;而只有在需要保留重复元素的少数情况下,我们使用 multiset ,下题就是一个例子。

一些其他性质

由于这位不小心故意把代码打错了,于是有了这一框,后续发现奇特的性质也会添加。

错误代码部分:

  1. if(s[1]<s[2])
  2. swap(s[1],s[2]);

意外地发现题目能过,初步猜测对两个 set 比较返回的是 \(size\) 之间的比较。

但经过一定的探讨后发现,这里比较的是两个容器队首元素的字典序(神奇)

探究过程

假说演绎法?

  1. s[1].insert(99);
  2. s[1].insert(2);
  3. s[2].insert(3);
  4. s[2].insert(55);
  5. s[2].insert(66);
  6. if(s[1]<s[2])
  7. cout<<"1";
  8. else
  9. cout<<"2";

如果排序准则为 size ,那么这里有 \(s[i].size()=2\)\(s[2].size()=3\),应该输出2,但结果输出的是1;

我们将代码第二行改为 s[1].insert(4) 后,输出结果变成了2;

于是我们发现,这里的比较与 \(s.size()\) 无关,而与队首元素值有关,经过一些资料的搜寻,我们得到结论:

将两个 set 容器进行比较,实质为比较两个容器队首元素的字典序。

PART 2 大根堆

题面

题库
image

思路

常规办法为线段树合并,但太长了不想写 但总感觉有更优的做法,所以就有了下面基于 set 优化的启发式合并做法。

我们可以通过 dp 引入,固定一点作为根结点,用f[u][i]表示以 \(u\) 为树根的子树里结点权值小于 \(i\) 的个数,其中 \(i\le v_u\);用 \(size_u\) 表示以 \(u\) 为根的树的大小。

那么很容易能想到状态转移方程为:

\[f[u][i]=\sum_{size_u}f[u][i],i\le v_u \]

\[f[u][i]=max(\sum_{size_u}f[u][i],\sum_{size_u}f[u][i+1]-1),i\ge v_u \]

那么如何存储? multiset !

set 自带的查找功能可以很方便的判断条件是否成立, \(size\) 函数也直接提供了上述 \(size\) 数组。

使用 dfs 进行遍历,由子结点逐个回溯至根节点,最后根节点的 \(size\) ,即为答案。

代码中加入了部分注释,供参考。

code:

  1. #include<bits/stdc++.h>
  2. inline int qr()
  3. {
  4. char ch=getchar();int x=0,f=1;
  5. for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
  6. for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
  7. return x*f;
  8. }
  9. #define qr qr()
  10. using namespace std;
  11. const int Ratio=0;
  12. const int N=500005;
  13. int n,cnt;
  14. int va[N],hh[N],to[N],ne[N];
  15. multiset<int>f[N];
  16. namespace Wisadel
  17. {
  18. void Wadd(int u,int v)
  19. {//加边
  20. to[++cnt]=v;
  21. ne[cnt]=hh[u];
  22. hh[u]=cnt;
  23. }
  24. void Wdfs(int u,int fa)
  25. {
  26. for(int i=hh[u];i!=-1;i=ne[i])
  27. {//遍历
  28. int t=to[i];
  29. if(t==fa)
  30. continue;
  31. Wdfs(t,u);
  32. if(f[u].size()<f[t].size())
  33. swap(f[u],f[t]);
  34. //堆,所以父节点的大小应大于子节点
  35. for(auto j:f[t])
  36. f[u].insert(j);
  37. //合并
  38. f[t].clear();
  39. //擦去被合并了的树
  40. }
  41. if(f[u].size()>0&&f[u].lower_bound(va[u])!=f[u].end())
  42. f[u].erase(f[u].lower_bound(va[u]));
  43. //因为是大根堆,所以子应小于父
  44. //那么若存在比父大的元素,这个堆便不成立
  45. //擦去它
  46. f[u].insert(va[u]);
  47. //把当前结点(根节点)插入
  48. }
  49. short main()
  50. {
  51. memset(hh,-1,sizeof hh);
  52. n=qr;
  53. for(int i=1;i<=n;i++)
  54. {
  55. va[i]=qr;int b=qr;
  56. if(i!=1)
  57. Wadd(i,b),Wadd(b,i);
  58. }
  59. Wdfs(1,-1);
  60. printf("%d\n",(int)f[1].size());
  61. //我们所用dfs的遍历形式,保证了会先将尽头的子树遍历尽
  62. //所以每个结点遍历后的结果,是包含了它以及它子树的最优解
  63. //所以经过交换,最后答案会体现在f[1]容器内元素的个数
  64. return Ratio;
  65. }
  66. }
  67. int main(){return Wisadel::main();}

PART 3 领导集团问题

题面

洛谷 题库
image

思路

与上题大根堆思路基本一致,这道题要求形成小根堆,只需加一点修改即可。

  1. 输入是分步输入,连边时改成了 \(v_i\)\(i+1\) 连边(不会有人注意不到吧 除了我);
  2. 由于小根堆子大于父,应擦去比父小的元素,也就是判断时改为与 f.begin() 进行比较;
  3. 删除元素时,由于 lower_bound 返回的是第一个不小于的值,应删除的是找到的元素的上一个,需要用到上面提到过的 iterator 迭代器,详见代码。

另外,这道题的数据范围明显增大,由于用到了 set 容器,不能随心所欲一次开最大(悲,所以要稍微缩小一点。

code:

为防止篇幅过长,我仅保留了主要的代码部分。

  1. const int N=2000005;
  2. int n,cnt;
  3. int va[N],hh[N<<1],to[N<<1],ne[N<<1];
  4. multiset<int>f[N];
  5. namespace Wisadel
  6. {
  7. void Wadd(int u,int v)
  8. {//加边
  9. to[++cnt]=v;
  10. ne[cnt]=hh[u];
  11. hh[u]=cnt;
  12. }
  13. void Wdfs(int u,int fa)
  14. {
  15. for(int i=hh[u];i!=-1;i=ne[i])
  16. {//遍历
  17. int t=to[i];
  18. if(t==fa)
  19. continue;
  20. Wdfs(t,u);
  21. if(f[u].size()<f[t].size())
  22. swap(f[u],f[t]);
  23. for(auto j:f[t])
  24. f[u].insert(j);
  25. f[t].clear();
  26. }
  27. multiset<int>::iterator it=f[u].lower_bound(va[u]);
  28. //使用iterator迭代器先找到 lower_bound(va[u]) 的位置
  29. if(f[u].size()>0&&it!=f[u].begin())
  30. f[u].erase(--it);
  31. //这个迭代器可以进行自增,自减等运算,很方便
  32. f[u].insert(va[u]);
  33. }
  34. short main()
  35. {
  36. memset(hh,-1,sizeof hh);
  37. n=qr;
  38. fo(i,1,n)
  39. va[i]=qr;
  40. fo(i,2,n)
  41. {
  42. int a=qr;
  43. Wadd(i,a),Wadd(a,i);
  44. }
  45. Wdfs(1,-1);
  46. printf("%d\n",(int)f[1].size());
  47. return Ratio;
  48. }
  49. }
  50. int main(){return Wisadel::main();}

完结撒花~

image

原文链接:https://www.cnblogs.com/Ratio-Yinyue1007/p/18186604

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号