zoukankan      html  css  js  c++  java
  • 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 << "----------------------- 
    遍历操作: 
    ";
          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 << "----------------------- 
    迭代器操作: 
    ";
          for (vector<int>::iterator it = beg; it != ed; ++it) 
          {
              cout << *it << endl; 
          }
      ​
          //这里演示c++11的简单方法, 蓝桥杯貌似不支持
          cout << "--------------------- 
    c++11标准: 
    ";
          //这里使用的叫做范围for循环, 不需要指定类型,由编译器自动完成 
          for (auto &e : v)         
          {
              cout << e << endl;  
          } 
          
          // 顺序容器的一些操作 
          cout << "
    容器的元素的size: " << v.size() << endl;
          
          cout << "
    容器删除指定位置元素, " ;
          //删除第4个元素, vector的删除和插入元素操作效率都比较低, 不适合大批操作
          v.erase(v.begin() + 3);    
          
          cout << "删除第4个元素后容器: 
    "; 
          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 << "迭代器遍历: 
    ";
          //迭代器, 蓝桥杯支持
          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 << "
    删除了元素1之后查找1: ";  
          //其他查找元素的方法
          if (s.count(1) != 0) {
              cout << "Found !
    ";
          } 
          else {
              cout << "Not found !
    ";
          }
          
          cout << "
    遍历所有元素: 
    ";
          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 
    ";
          } 
          else {
              cout << "not found !
    ";
          }    
          
          m.erase(10);
          
          //遍历一遍所有元素
          cout << "
    遍历所有元素: 
    ";
          for (ite = m.begin(); ite != m.end(); ++ite) {
              printf("%d: %s
    ", ite->first, ite->second);
          } 
          
          cout << "
    范围for循环: 
    ";
          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 << "栈为空
    ";
          }
          
          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 << "队列为空
    ";
          }
          
          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 << "
    
    升序: ";
          sort(a, a + 13, less<int>());
          
          for (const auto& e: a) {
              cout << e << " ";
          }
          
          cout << "
    
    ";
          //自定义排序
          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 << "
    使用STL中的next_permutation: 
    ";
          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
    ", 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
    ", 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
    ", ans);
      }
      ​
      int main()
      {
          solve();
          
          return 0;   
      }
    类似题目:
    • 二分查找

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

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

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

  • 相关阅读:
    luogu P2439 [SDOI2005]阶梯教室设备利用
    bzoj1559: [JSOI2009]密码
    bzoj3172: [Tjoi2013]单词
    后缀树简短实现
    [APIO2010]特别行动队 --- 斜率优化DP
    [APIO2014]序列分割 --- 斜率优化DP
    [HNOI2012]集合选数 --- 状压DP
    UVA11107 Life Forms --- 后缀数组
    [TJOI2017]DNA --- 后缀数组
    [NOI2014]购票 --- 斜率优化 + 树形DP + 数据结构
  • 原文地址:https://www.cnblogs.com/douzujun/p/7848419.html
Copyright © 2011-2022 走看看