STL在算法比赛中简单应用

STL在算法比赛中简单应用

STL基础 和 简单的贪心问题

STL(Standard Template Library) 标准模板库。 它包含了诸多在计算机科学领域里所常用的基本数据结构和算法。这些数据结构可以与标准算法一起很好的工作。

这里我们主要是为了学会如何使用,如果想了解更多,可以参考《c++ primer 第五版》

  • vector

vector 就是一个 不定长数组。它把一些常用的操作 “封装” 在了vector类型内部。

vector是一个模板类,所以需要vector<int> a 或者 vector<string> b 这样的方式来声明一个 vector对象。

vector<int> 类似于 int a[] 的整数数组, 而vector<string> b 相当于 string b[] 的字符串数组。

下面用代码介绍,常用的操作:

  #include <iostream>
  #include <vector>       //使用vector需要引入这个头文件
  #include <iterator>     //这是使用迭代器需要引入的头文件 
using namespace std;
  ​
  int main()
  {
      vector<int> v;      //定义一个vector容器
      
      //以下操作向 v对象中,连续添加5个元素 1, 2, 3, 4, 5 
      v.push_back(1);     //向容器添加元素1 
      v.push_back(2);
      v.push_back(3);
      v.push_back(4);
      v.push_back(5);
      
      //遍历向量的元素( 这里不需要c++11的标准, 蓝桥杯应该也是支持的), C遍历的写法 
      cout << "----------------------- \n遍历操作: \n";
      for (unsigned i = 0; i < v.size(); i++)
      {
          cout << v[i] << endl; 
      }
      
      vector<int>::iterator beg = v.begin();   //指向容器的第一个元素
      vector<int>::iterator ed = v.end();      //指向容器 尾元素的  下一个位置!注意是下一个位置  
      
      cout << "----------------------- \n迭代器操作: \n";
      for (vector<int>::iterator it = beg; it != ed; ++it) 
      {
          cout << *it << endl; 
      }
  ​
      //这里演示c++11的简单方法, 蓝桥杯貌似不支持
      cout << "--------------------- \nc++11标准: \n";
      //这里使用的叫做范围for循环, 不需要指定类型,由编译器自动完成 
      for (auto &e : v)         
      {
          cout << e << endl;  
      } 
      
      // 顺序容器的一些操作 
      cout << "\n容器的元素的size: " << v.size() << endl;
      
      cout << "\n容器删除指定位置元素, " ;
      //删除第4个元素, vector的删除和插入元素操作效率都比较低, 不适合大批操作
      v.erase(v.begin() + 3);    
      
      cout << "删除第4个元素后容器: \n"; 
      for (auto &e : v) {
          cout << e << endl;
      }
      
      //与vector有关的其他操作,以及类似的还有链表list,可以参考:
      //http://zh.cppreference.com/w/cpp/container/vector 
      
      return 0;   
  }
  • string类

可以动态存储字符串,并且集成了一堆对字符串的操作

#include <iostream>
  #include <string>using namespace std;
  ​
  ​
  int main()
  {
      /*---------------------string容器的一些操作-----------------------*/
      string str1 = "Hello Douzi";            // string的几种构造方法
      string str2("Hello World");        
      string str3(str1, 6);                  // 从str1下标6开始构造, str3 == 'Douzi'
      
      string str4 = str2.substr(0, 5);       // 求子串: str4 -> Hello
      string str5 = str2.substr(6);          // 求子串: str5 -> World
      string str6 = str2.substr(6, 11);      // 求子串: str6 -> World
  //     string str7 = str2.substr(12);      // 抛异常: out_of_range
string str8 = str2.replace(6, 5, "Game");    // 替换:str8 -> Hello Game 从位置6开始,删除5个字符,并替换成"Game"
string str9 = str2.append(", Hi girl");      // 追加字符串: str9 -> Hello World, Hi girl
// 查找字符串    : pos1 -> 6 ,返回第一次出现字符串的位置,如果没找着,则返回npos
      auto pos1 = str1.find("Douzi");    
      cout << "Douzi 第一次出现在str1中位置: " << pos1 << endl;           
  ​
      string ss1 = "Dou";
      string ss2 = "zi";
      
      if (ss1 == ss2) 
      {
          cout << "ss1 == ss2" << endl;
      } 
      else if (ss1 < ss2)
      {
          cout << "ss1 < ss2" << endl;    
      }
      else if (ss1 > ss2) 
      {
          cout << "ss1 > ss2" << endl;    
      }
      
      string ss3 = ss1 + ss2;
      cout << "ss1 + ss2 = " << ss3 << endl; 
      cout << "ss2 + ss1 = " << ss2 + ss1 << endl;
      
      return 0;
  }
  • set (关联容器)

是用 二查搜索树 维护集合的容器;

简单的说就是,里面存放的数据: 每个元素最多只出现一次,且里面元素 有序 (可以自定义排序)

 
  #include <iostream>
  #include <set>
  #include <algorithm>
  #include <iterator>
  #include <cstdio>using namespace std;
  ​
  int main()
  {
      //声明
      set<int> s;
      
      //插入元素
      s.insert(1);
      s.insert(5);
      s.insert(3);
      s.insert(8);
      s.insert(4);
      
      // c++11, 蓝桥杯不支持 
      for (auto &e : s) {
          cout << e << endl;
      }
  ​
      cout << "迭代器遍历: \n";
      //迭代器, 蓝桥杯支持
      for (set<int>::iterator it = s.begin(); it != s.end(); ++it) 
      {
          cout << *it << endl;    
      } 
  ​
      //集合的元素的不重复的
      s.insert(3);
      cout << "插入一个重复的元素之后: " << endl;
      for (const auto& e : s) {
          cout << e << endl;
      } 
      
      //查找元素
      set<int>::iterator ite;
      
      ite = s.find(1);
      cout << "查找元素1 : ";
      if (ite == s.end()) {
          printf("not found !");
      } 
      else {
          cout << "Found !" << endl;
      }
      
      //删除元素
      s.erase(1);
      
      cout << "\n删除了元素1之后查找1: ";  
      //其他查找元素的方法
      if (s.count(1) != 0) {
          cout << "Found !\n";
      } 
      else {
          cout << "Not found !\n";
      }
      
      cout << "\n遍历所有元素: \n";
      for (auto &e : s) {
          cout << e << endl;
      }
      
      return 0;
  }
  • map(关联容器)

map 是 维护键 和 键对应的值 的容器.

即是: 实现从 键 (key)值(value) 的映射。map重载了[] 运算符, map像 高级版的数组

如: 可以用一个 map<string, int> mon_name 来表示 "月份名字到月份编号" 的映射, 然后用 mon_name["july"] = 7 这样的方式来赋值。

  #include <iostream>
  #include <map>
  #include <string>
  #include <utility>
  #include <cstdio>
  using namespace std;
  ​
  int main()
  {
      //声明(int为键, const char *为值)
      map<int, const char*> m;
      
      //插入元素
      m.insert(make_pair(1, "ONE"));
      m.insert(make_pair(10, "TEN"));
      m[100] = "Hundred";
      
      //查找元素
      map<int, const char*>::iterator ite;
      ite = m.find(1);
      
      if (ite == m.end()) {
          cout << "not found !" << endl;  
      }   
      else {
          cout << ite->second << " is found " << endl;
      }
      
      //另外一种查找方式
      if (m.count(10) != 0) {
          cout << m[10] << " is found \n";
      } 
      else {
          cout << "not found !\n";
      }    
      
      m.erase(10);
      
      //遍历一遍所有元素
      cout << "\n遍历所有元素: \n";
      for (ite = m.begin(); ite != m.end(); ++ite) {
          printf("%d: %s\n", ite->first, ite->second);
      } 
      
      cout << "\n范围for循环: \n";
      for (const auto& e: m) {
          cout << e.first << ": " << m[e.first] << endl;
      }
      
      // 可以写一下这个简单题: http://lx.lanqiao.cn/problem.page?gpid=T411
      return 0;
  }
  • stack

栈(Stack) 是支持 push 和 pop两种操作的数据结构。

push是在栈的顶端放入一组数据的操作。

pop 是从其顶端取出一组数据的操作。但他仅仅是 移除最顶端的数据,如果要访问最顶端的数据,需要使用 stack::top函数

  #include <iostream>
  #include <stack>
  #include <cstdio>using namespace std; 
  ​
  int main()
  {
      stack<int> s;       //声明存储int类型的数据的栈 
      s.push(1);          //{} -> {1}
      s.push(5);          //{1} -> {1, 5}
      s.push(3);          //{1, 5} -> {1, 5, 3} 
      
      cout << s.top() << endl;  // 3
      
      s.pop();            //从栈顶移除{1, 5, 3} -> {1, 5}
      cout << s.top() << endl;  // 5
      
      s.pop();            //从栈顶移除{1, 5} -> {1}
      cout << s.top() << endl;  // 1
      
      s.pop();
      if (s.empty()) {
          cout << "栈为空\n";
      }
      
      return 0;
  }
  • queue

队列是 先进先出 的数据结构

用push() 和 pop() 进行元素的入队和出队; front() 取队首元素 (但不删除)

队列在 BFS搜索的题目中会经常用到,需要会使用

  
  #include <iostream>
  #include <queue>using namespace std;
  ​
  int main()
  {
      queue<int> que;        //声明存储int类型的数据的队列
      que.push(1);           //{} -> {1}
      que.push(2);           //{1} -> {1, 2}
      que.push(3);           //{1,2} -> {1, 2, 3} 
      cout << que.front() << endl;   // 1
      
      que.pop();             //从队首移除 {1,2,3} -> {2, 3} 
      cout << que.front() << endl;   // 2
      
      que.pop();             //从队首移除 {2,3} -> {3} 
      cout << que.front() << endl;   // 3
      
      que.pop();
      if (que.empty()) {
          cout << "队列为空\n";
      }
      
      return 0;
  } 
  • sort

c++ 自带的排序

  
  #include <iostream>
  #include <algorithm>
  #include <functional>
  #include <cstdio>
  #include <string>using namespace std;
  ​
  struct Stu {
      int score;
      int rp;
      string name;
      
      Stu(string name = "", int score = 0, int rp = 0) :
          name(name), score(score), rp(rp) {} 
  }; 
  ​
  //按(分数+人品)升序 排序 
  bool cmp(const Stu& a, const Stu& b)
  {
      return (a.score + a.rp) < (b.score + b.rp);
  }
  ​
  int main()
  {
      int a[] = {1, 7, 10, 5, 22, 5, 16, 8, 11, 3, 54, 100, 23};
      
      cout << "降序: ";
      sort(a, a + 13, greater<int>());
      
      for (int i = 0; i < 13; i++) {
          cout << a[i] << " ";
      }
      
      cout << "\n\n升序: ";
      sort(a, a + 13, less<int>());
      
      for (const auto& e: a) {
          cout << e << " ";
      }
      
      cout << "\n\n";
      //自定义排序
      Stu s[] = {Stu("Douzi", 59, 60), Stu("Baozi", 100, 1), Stu("douzujun", 10, 1000)};
  ​
      sort(s, s + 3, cmp);
      
      for (int i = 0; i < 3; i++) 
      {
          cout << s[i].name << ": " << s[i].score << " " << s[i].rp << endl;  
      }
      
      cout << endl;
      
      return 0;
  } 
  • next_permutation

  1. 全排列问题

  
  #include <iostream>
  #include <algorithm>
  using namespace std;
  ​
  const int MAX_N = 100000;
  bool used[MAX_N];
  int perm[MAX_N];
  ​
  //生成{0,1,2,3....n-1}的 n! 排序 
  void permutation1(int pos, int n) 
  {
      if (pos == n) {
          /*这里编写要对perm进行的操作*/
          for (int i = 0; i < pos; i++)
              cout << perm[i] << " ";
          cout << endl;
          return;
      }
      //针对perm的第pos个位置, 究竟使用0~n-1中哪个进行循环
      for (int i = 1; i <= n; i++) {
          if (!used[i]) {
              perm[pos] = i;
              used[i] = true;
              permutation1(pos + 1, n);
              //返回之后把标志复位 -- 否则一次就结束了 
              used[i] = false;   
          }
      } 
      return;
  }
  ​
  void solve2(int n)
  {
      int perm2[] = {1, 2, 3};
      
      do {
          for (int i = 0; i < n; i++) {
              cout << perm2[i] << " ";
          }
          cout << endl;
      } while (next_permutation(perm2, perm2 + n));
  }
  ​
  int main()
  {
      permutation1(0, 3); 
      
      cout << "\n使用STL中的next_permutation: \n";
      solve2(3);
  }
  1. 往年蓝桥杯中用next_permutation能够快速解题的

观察下面的加法算式:

​   祥 瑞 生 辉

+ 三 羊 献 瑞

-------------------

三 羊 生 瑞 气

其中,相同的汉字代表相同的数字,不同的汉字代表不同的数字。

请你填写“三羊献瑞”所代表的4位数字(答案唯一),不要填写任何多余内容。(这是网上复杂的解法)

下介绍用 next_permutation 的简单写法

  
  #include <iostream>
  #include <algorithm>
  using namespace std;
  ​
  void solve()
  {
      int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
      
      int b, c,
          sum;
      
      do {
          b = a[0] * 1000 + a[1] * 100 + a[2] * 10 + a[3];
          c = a[4] * 1000 + a[5] * 100 + a[6] * 10 + a[1];
          
          sum = a[4] * 10000 + a[5] * 1000 + a[2] * 100 + a[1] * 10 + a[7];
          
          if ( (c > 999) && (b + c == sum) ) {
              cout << c << endl;
          }
          
      } while (next_permutation(a, a + 10));
  }
  ​
  int main()
  {
      solve();
  }
课后习题:
  1. 问题二(会用到DFS): 带分数 http://lx.lanqiao.cn/problem.page?gpid=T26

  • 简单的贪心问题

  • 硬币问题

有1元、5元、10元、50元、100元、500元的硬币各C1,C5,C10,C50,C100,C500枚。现在要用这些硬币来支付A元,最少 需要多少枚硬币?假设本题至少存在一种支付方案。

限制条件: 0<=C1,C5,C10,C50,C100,C500<=10的9次方 ; 0<= A <= 10的9次方

输入: C1 = 3 C2 = 2 C10 = 1 C50 = 3 C100 = 0 C500 = 2 A = 620

输出: 6(500元硬币1枚,50元硬币2枚,10元硬币1枚,5元硬币2枚,合计6枚)

解题思路: 优先选择最大金额的面额

  
  #include <iostream>
  #include <algorithm> 
  using namespace std;
  ​
  const int maxn = 6 + 10;
  //硬币的面值 
  const int V[6] = {1, 5, 10, 50, 100, 500};  
  //输入 
  int C[maxn];  // c[0] = C_1, C{1] = C_5....
  int A;        //支付A元 
/*
  3 2 1 3 0 2 620
  */
  void solve()
  {
      int ans = 0;
      
      for (int i = 5; i >= 0; i--) 
      {
          int t = min(A / V[i], C[i]); //使用硬币i的枚数
          A -= t * V[i];               //还需支付的金额
          ans += t;                     
      }
      
      printf("%d\n", ans);
  }   
  ​
  void input()
  {
      for (int i = 0; i < 6; i++)
      {
          cin >> C[i];
      }
      cin >> A;
  }
  ​
  int main()
  {
      input();
      solve();
  }
类似联系:蓝桥杯 算法提高 快乐司机

题目链接: http://lx.lanqiao.cn/problem.page?gpid=T321

话说现在当司机光有红心不行,还要多拉快跑。多拉不是超载,是要让所载货物价值最大,特别是在当前油价日新月异的时候。司机所拉货物为散货,如大米、面粉、沙石、泥土......  现在知道了汽车 核载重量为w,可供选择的物品的数量n。每个物品的重量为gi, 价值为pi。求汽车可装载的最大价值。(n<10000,w<10000,0<gi<=100,0<=pi<=100)

输入格式

  输入第一行为由空格分开的两个整数n w  第二行到第n+1行,每行有两个整数,由空格分开,分别表示gi和pi

输出格式

  最大价值(保留一位小数)

样例输入

5 3699 8768 3679 4375 947 35

样例输出

71.3解释:先装第5号物品,得价值35,占用重量7再装第4号物品,得价值36.346,占用重量29最后保留一位小数,得71.3

由这个解释可以看出来: 物品可以分开装载到车上,不一定是不够装就不装了,而是装上能装的部分

显然,既然可以散装,那么排序的时候,应该用单价来进行升序排序!即每次都优先选择,单价高的物品,当不够装的时候,将剩余的重量全部 用来给 当前单价最高的物品放置

  
  #include <iostream>
  #include <cstdio>
  #include <algorithm>
  #include <cstdlib>
  using namespace std;
  ​
  const int maxn = 10000 + 200;
  struct Lorry {
      float weight;
      float value;
      float pro;
      Lorry(float w = 0, float v = 0, float pro = 0) :
          weight(w), value(v), pro(pro) {}
  } lorry[maxn]; 
  ​
  //按价值率降序排序 
  bool cmp(const Lorry& a, const Lorry& b)
  {
      return a.pro > b.pro;
  }
  ​
  void solve();
  ​
  void solve()
  {
      int n;
      float w; //物品数量, 核载重量 
      float ans = 0;
      scanf("%d", &n);
      cin >> w;
      for (int i = 0; i < n; i++) {
          cin >> lorry[i].weight >> lorry[i].value;
          lorry[i].pro = lorry[i].value / lorry[i].weight;
      }
      //价值高的在前 
      sort(lorry, lorry + n, cmp);
      
      for (int i = 0; i < n; i++)
      {
          if (w == 0) break;
          //w > l[i].weight 放心减就好了 
          if (w - lorry[i].weight > 0) {
              w -= lorry[i].weight;
              ans += lorry[i].value;
          } else {
              //否则..全部用来给w, 还有剩余,单价最高嘛 
              ans += lorry[i].pro * w;
              lorry[i].weight -= w;
              w = 0;
          }
      }
      printf("%.1f\n", ans);
  }
  ​
  int main()
  {
      solve();
      return 0;    
  }
类似练习: Codevs 1621 混合牛奶

http://codevs.cn/problem/1621/

类似练习: Codevs 1052 地鼠游戏 http://codevs.cn/problem/1052/

关键:

游戏开始时,会在地板上一下子冒出很多地鼠(同时出现的) 来,然后等你用榔头去敲击这些地鼠,每个地鼠被敲击后,将会增加相应的游戏分值。问题是这些地鼠不会傻傻地等你去敲击,它总会在冒出一会时间后又钻到地板下面去(而且再也不上来),每个地鼠冒出后停留的时间可能是不同的,而且每个地鼠被敲击后增加的游戏分值也可能是不同,为了胜出,游戏参与者就必须根据每个地鼠的特性,有选择地尽快敲击一些地鼠,使得总的得分最大。

(敲击每个地鼠所需要的耗时是1秒),而且他还发现了游戏的一些特征,那就是每次游戏重新开始后,某个地鼠冒出来后停留的时间都是固定的,而且他记录了每个地鼠被敲击后将会增加的分值。于是,他在每次游戏开始后总能有次序地选择敲击不同的地鼠,保证每次得到最大的总分值

输入描述 Input Description

输入包含3行,第一行包含一个整数n(1<=n<=100)表示有n个地鼠从地上冒出来,第二行n个用空格分隔的整数表示每个地鼠冒出后停留的时间,第三行n个用空格分隔的整数表示每个地鼠被敲击后会增加的分值(<=100)。每行中第i个数都表示第i个地鼠的信息。

输出描述 Output Description

输出只有一行一个整数,表示王钢所能获得的最大游戏总分值。

样例输入 Sample Input

5

5 3 6 1 4

7 9 2 1 5

样例输出 Sample Output

24

题解: 地鼠具有两个属性(时间, 分值),所以我考虑将地鼠设置为结构体, 包含时间和分值,将地鼠按照时间自定义升序排序。这里有点动态规划的意思,

 #include <iostream>
  #include <algorithm>
  #include <cstdio>
  #include <cstring>
  using namespace std;
  ​
  const int maxn = 100 + 50;
  struct Mice {
      int value;
      int time;
      Mice(int v = 0, int t = 0): value(v), time(t) {}
      //需要对时间进行排序 -- 升序
      friend operator < (const Mice& a, const Mice& b) {
          return a.time < b.time;
      } 
  } game[maxn];
  ​
  int dp[maxn], ans;
  ​
  void solve();
  ​
  void solve()
  {
      int n;
      memset(dp, 0, sizeof(dp));
      cin >> n;
      for (int i = 0; i < n; i++) {
          cin >> game[i].time;
      }
      for (int i = 0; i < n; i++) {
          cin >> game[i].value;
      }
      
      //按照时间升序
      sort(game, game + n);
      for (int i = 0; i < n; i++)
      {
          //从时间小的开始打
          for (int j = game[i].time; j >= 1; j--)
          {
              //game[i].time为当前最小的时间
              //j为动态减小的时间
              //比较"当前时间长的value+之前时间短的价值" 与 "当前时间长的value",更新j时间价值高的
              dp[j] = max(dp[j], dp[j - 1] + game[i].value);
              //更新价值更高的值
              ans = max(ans, dp[j]); 
          } 
      }
      printf("%d\n", ans);
  }
  ​
  int main()
  {
      solve();
      
      return 0;   
  }
类似题目:
  • 二分查找

文章参考了: 《c++ primer》、《算法竞赛入门经典》、《挑战程序设计竞赛》

以及谭兆飞学长的博客:http://www.jianshu.com/p/26d4d60233a

以及我自己的博客:http://www.cnblogs.com/douzujun/category/973918.html

posted @ 2017-11-17 02:03 douzujun 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值