17年真题总结
17.17年:
17.8:(完全背包问题)t8思路安装数学思维:如果输入的n个数的最大公约数不是是1那么他们都是质数,则凑不出的数就是无穷多个;
接着我们考虑n个数的最大公约数是1的情况,比如4,5,6这三个数,这题有点背包问题的思想利用dp数组的思维,dp[i][j]
表示前i个数是否可以凑成j(fales/ture);初始状态确定以后,后面的数一定是前面的数加4或者5或者6来的,分成了3种状态
for (int j = 0; j < 10000; ++j) {
if (f[j])f[j + a[i]] = true;
}
为什么上限定为1w,这个
t8和13年的t8买不到的数相呼应
gcd()是一个求最大公约数的函数
//求二数的最小公约数
int gcd(int a, int b) {
if (b == 0)return a;
return gcd(b, a % b);
}
17.9-分巧克力
该题的思路是搜索适合的最大边长,一步一步的进行枚举,主要计算代码如下
for (int i = 0; i < n; ++i) {
cnt += (h[i] / mid) * (w[i] / mid);
}
mid为枚举数cnt为该枚举数值下的分块个数;
为了进一步的优化,降低时间复杂度,可以利用二分法的方法去查找这个数(这里的代码是值得学习的)
17.10 -刷油漆
int abs(int i) //返回整型参数i的绝对值
double fabs(double x) //返回双精度参数x的绝对值
VC++与vs默认的栈空间是1M,超出就会发生越界
计算数组内存
如int num[900][900];
计算公式:900900sizeof(int)/1024 = 3164KB=3MB > 2MB所以溢出(VS2010下的测试)
解决办法:写成全局变量
max、min函数的头文件为:#include
17年总结:
01迷宫 暴力dfs
02跳蚱蜢 bfs
03魔方状态 模拟+判重(太难了)
04方格分割沿着中心点剪裁, dfs
05字母组串 递归,参数的含义及变化的方向
06最大公共子串 dp
07正则问题 复杂递归
08包子凑数 扩展欧几里得,完全背包问题的变体
09分巧克力 二分枚举
10油漆面积 线段树+扫描线+矩形面积
蓝桥官网练习集:
t1: 公式求组
1.数值过大时使用long long型
2.相除相减的二个数不能同时取余;相乘相加的二个数可以同时取余
3.对于公式检测是否可以简化的情况
t2: 九宫重排(练习bfs)
1.使用广度搜索模板
2.使用字符串来代表状态
3.熟悉map的特性
t3: 刷油漆
1.对于搜索题,如果深度太大,势必会超时,选择动态规划是一个比较好的选择
2.动态规划的难题是先进行题意理解如何写出状态转移方程,这往往是最难的地方,还有就是定义dp的含义
t4: 错误票据
1.int num[1000] while(cin>>num[k]) //蓝桥杯是以文件的形式输入的
2. sort(num,num+k) //数组排序
3. 该题难度不大 但题目限制条件多
t5: 翻硬币(练习bfs)
1. 需要利用广度度优先搜索算法bfs,但当数量比较大时用例通过不了(N<1000)
2. 其实吧,这道题目可以不用广度搜索,简单方法就是从头开始检查不同,不同的翻,检查完的就不用管了
第十届:
t1: 枚举全部
1.bool f(int x){ //这个函数可以分解位数从而进行判断
while(x>0){
int t=x%10;
if(t2||t0||t1||t9)return 1;
x/=10;
}
return 0;
}
t2: 取模对加乘没有影响
一些技巧:
- 仔细看题,不要理解错题意。多测试几组数据
2:编程填空题 直接复制代码 填了空测试 所填代码是否正确,可能没有思路 可以尝试一下手算
3:有的题写不出来最优的算法,也要要暴力的算法写出来,过部分数据
4:编程题 有大量相同的代码块时 直接复制粘贴 不要手打 节约时间
5:填空题直接暴力枚举,求答案
6:蓝桥杯很多题都是可以通过枚举排列,枚举子集来得出答案
7:写编程题先审清楚好题意,判断时间复杂度和空间复杂度在写。没把握就先别写,免得浪费时间。
8: 万能头文件(#include<bits/stdc++.h>)也可以用。
9: 能用到函数的地方尽量用函数,比如sort、next_permutation等,省时省力,STL同理。
10: 代码填空:一般是递归、找规律等,实在想不出来可以随便试,很容易试对
11: 有一句话叫“如果询问的两点不连通则输出-1”,这句话隐含意义就是后台至少有一组测试样例是不连通的。
那与其空着不写,不如你就写个程序输出-1,假如后台有5组测试样例,你也拿到了1/5的分数。
常用的stl容器:
1.vector
特性: 顺序容器,vector 容器与数组相比其优点在于它能够根据需要随时自动调整自身的大小以便容下所要放入的元素。
此外, vector 也提供了许多的方法来对自身进行操作。
头文件:
#include<vector>
常用函数:
#声明
vector<int> a ; //声明一个int型向量a
vector<int> a(10) ; //声明一个初始大小为10的向量
vector<int> a(10, 1) ; //声明一个初始大小为10且初始值都为1的向量
vector<int> b(a) ; //声明并用向量a初始化向量b
vector<int> b(a.begin(), a.begin()+3) ; //将a向量中从第0个到第2个(共3个)作为向量b的初始值
#基本操作
a.size() //获取向量中的元素个数
a.empty() //判断向量是否为空
a.clear() //清空向量中的元素
a.push_back(c); 尾部插入数字:
c.reserve(100); 定义空间为100
cout<<a[0]<<endl; 使用下标访问元素,记住下标是从0开始的。
使用迭代器访问元素:
vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<<endl;
插入:
vec.insert(vec.begin()+i,a); 插入元素,在第i+1个元素前面插入a;
vec.erase(vec.begin()+2); 删除元素,删除第3个元素
vec.erase(vec.begin()+i,vec.end()+j);删除区间[i,j-1];区间从0开始
排序:
sort(vec.begin(),vec.end());
反转:
reverse(vec.begin(),vec.end());
2.map
头文件:
#include <map>
特性:
C++中map提供的是一种键值对容器,里面的数据都是成对出现的,每一对中的第一个值称之为关键字(key),第二个称之为该关键字的对应值Values。每个关键字只能在map中出现一次。具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。
常用函数:
声明: map<int, string> ID_Name;
map<int, string> ID_Name = {
{ 2015, "Jim" },
{ 2016, "Tom" },
{ 2017, "Bob" } };
迭代器声明:map<int,int>::iterator it;
插入函数:
// 定义一个map对象
map<int, string> mapStudent;
// 第一种 用insert函數插入pair
mapStudent.insert(pair<int, string>(000, "student_zero"));
// 第二种 用insert函数插入value_type数据
mapStudent.insert(map<int, string>::value_type(001, "student_one"));
// 第三种 用"array"方式插入
mapStudent[123] = "student_first";
mapStudent[456] = "student_second";
///insert操作是不能在插入数据的,但是用数组方式就不同了,它可以覆盖以前该关键字对 应的值,
查找函数:
// find 返回迭代器指向当前查找元素的位置否则返回map::end()位置
iter = mapStudent.find("123");
if(iter != mapStudent.end())
cout<<"Find, the value is"<<iter->second<<endl;
else
cout<<"Do not Find"<<endl
删除与清空元素:
//迭代器刪除
iter = mapStudent.find("123");
mapStudent.erase(iter);
//用关键字刪除
int n = mapStudent.erase("123"); //如果刪除了會返回1,否則返回0
//用迭代器范围刪除 : 把整个map清空
mapStudent.erase(mapStudent.begin(), mapStudent.end());
//等同于mapStudent.clear()
// 查询map是否为空
bool empty();
// 查询map中键值对的数量
size_t size();
// 清空map,清空后的size为0
void clear();
erase() 删除一个元素
find() 查找一个元素
map.count(Key) 返回值为1或者0,1返回存在,0返回不存在,返回的是布尔类型的值,
3.queue
特性:它只允许在表的前端进行删除操作,只允许在表的后端进行插入操作;
头文件:#include <queue>
定义一个queue的变量 queue<Type> M
查看是否为空范例 M.empty() 是的话返回1,不是返回0;
从已有元素后面增加元素 M.push()
输出现有元素的个数 M.size() 返回个数
显示第一个元素 M.front()
显示最后一个元素 M.back()
清除第一个元素 M.pop()
4.deque(双端队列容器)
deque 容器也擅长在序列尾部添加或删除元素(时间复杂度为O(1)),而不擅长在序列中间添加或删除元素。
deque 容器也可以根据需要修改自身的容量和大小。
5.algorithm库常用函数
sort 函数:
bool complare(int a,int b) {
return a>b;
}
int a[10]={9,6,3,8,5,2,7,4,1,0};
sort(a,a+10, complare);
swap 函数:
int a=1, b=2;
swap(a,b);//实参传入
最值:
int a=1, b=2;
cout<<max(a,b); // 输出 2
cout<<min(a,b); // 输出 1
绝对值:
abs(x) //返回x的绝对值值 必须为int型
元素反转:
reverse(it1,it2)//将数组指针在[it1,it2)之间的元素或容器的迭代器在[it1,it2)范围内的元素进行反转。
全排列:
可对字符串全排序:next_permutation(str.begin(),str.end())
对数组全排序:next_permutation(a,a+n) //a[n]
//输出n=3的全排列
int main(){
int a[10] = {1, 2, 3};
do{
printf("%d%d%d",a[0], a[1], a[2]);
}while(next_permutation(a, a+3));
}
5.memset
头文件: #include<string.h>
解析: void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符
使用:
二维数组:
int test[5][5];
memset(test, 0, sizeof(test));
memset(&test[0][0], 0, sizeof(test));
一维数组:
int a[100];
快速初始化数组元素全部为0
memset(a,0,100 *sizeof(int));
6.set
参考质料:https://blog.csdn.net/zscdst/article/details/78669483
头文件:#include<set>
特性:各键值对的键和值对应相等,自行根据键的大小对存储的键值对进行排序,使用 set 容器存储的各个元素的值必须各不相同
最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。
使用:
初始化:std::set<std::string> myset{"http://c.biancheng.net/java/",
"http://c.biancheng.net/stl/",
"http://c.biancheng.net/python/"};
插入://向myset容器中插入新元素
myset.insert("http://c.biancheng.net/java/");
myset.erase("http://c.biancheng.net/java/");//删除
//find()函数查找键值,查找到就返回迭代器位置,如果查找不到返回end位置的迭代器
set<int>::iterator it;
it=mysite.find("http://c.biancheng.net/java/");
if(it != mysite.end()) printf("%d\n",*it);//找到了
//count()函数判断某个元素是否存在 存在返回true不存在返回fasle
if(mysite.count("http://c.biancheng.net/java/"))
迭代器: //利用双向迭代器,遍历myset
for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
cout << *iter << endl;
}
7.unordered_set
参考质料:https://blog.csdn.net/TamakiRinko/article/details/100923754
简介:unordered_set是基于哈希表,因此要了解unordered_set,就必须了解哈希表的机制。哈希表是根据关键码值而进行直接访问的数据结构,通过相应的哈希函数(也称散列函数)处理关键字得到相应的关键码值,关键码值对应着一个特定位置,用该位置来存取相应的信息,这样就能以较快的速度获取关键字的信息。(unordered_set内部解决冲突采用的是—-链地址法,当用冲突发生时把具有同一关键码的数据组成一个链表。如下图所示)
序集合容器(unordered_set)是一个存储唯一(unique,即无重复)的关联容器,容器中的元素无特别的秩序关系,该容器允许基于值的快速元素检索,同时也支持正向迭代。每当unordered_set内部进行一次扩容或者缩容,都需要对表中的数据重新计算,也就是说,扩容或者缩容的时间复杂度至少为O(n)。
实现方法:
头文件:
# include<unordered_set>
初始化:
unordered_set<string> example;
unordered_set<string> things {16}; 定义哈希表大小
unordered_set<string> words {"one", "two", "three", "four"};// 初始化列表
unordered_set<string> copy_wrds {words}; // Copy constructor 拷贝
插入:
// Returns a pair - an iterator & a bool value
auto pr = words.insert("ninety");
// 1st arg is a hint. Returns an iterator
auto iter = words.insert (pr.first, "nine");
查找: 调用 unordered_set 的 find() 会返回一个迭代器。这个迭代器指向和参数哈希值匹配的元素,如果没有匹配的元素,会返回这个容器的结束迭代器
auto search = example.find(2);
if (search != example.end())
获得容器大小,判断是否为空:
size(), empty()
删除:
1.调用unordered_set容器的成员函数clear()可以删除它的全部元素。
2.成员函数erase()可以删除容器中和传入参数的哈希值相同的元素。
8.multiset
简介:
set和map内部实现是基于RB-Tree,而unordered_set和unordered_map内部实现是基于哈希表。
multiset 跟set 类似,唯一的区别是允许键值重复;map与multimap类似,唯一的区别是允许键值重复
特点:
1.不再以键值对的方式存储数据,因为 set 容器专门用于存储键和值相等的键值对,因此该容器中真正存储的是各个键值对的值(value);
2.set 容器在存储数据时,会根据各元素值的大小对存储的元素进行排序(默认做升序排序);
3.存储到 set 容器中的元素,虽然其类型没有明确用 const 修饰,但正常情况下它们的值是无法被修改的;
4.multiset 容器和 set 容器唯一的差别在于,multiset 容器允许存储多个值相同的元素,而 set 容器中只能存储互不相同的元素。
c++代码:
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main() {
std::multiset<int> mymultiset{1,2,2,2,3,4,5};
cout << "multiset size = " << mymultiset.size() << endl;
cout << "multiset count(2) =" << mymultiset.count(2) << endl;
//向容器中添加元素 8
mymultiset.insert(8);
//删除容器中所有值为 2 的元素
int num = mymultiset.erase(2);
cout << "删除了 " << num << " 个元素 2" << endl;
//输出容器中存储的所有元素
for (auto iter = mymultiset.begin(); iter != mymultiset.end(); ++iter) {
cout << *iter << " ";
}
return 0;
}
结果:
multiset size = 7
multiset count(2) =3
删除了 3 个元素 2
1 3 4 5 8
7,二分查找函数:
头文件:#include<algorithm>
特性:
lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。
在从小到大的排序数组中,
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的
地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存
在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound()和upper_bound()
lower_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到
返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回
该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
pow()函数的头文件是#include<cmath>
#include <cstdio>库中的:
//读取一行文件流
string line;
getline(cin, line);
//格式化读取字符串
int h1, m1, s1, h2, m2, s2, d;
sscanf(line.c_str(), "%d:%d:%d %d:%d:%d (+%d)", &h1, &m1, &s1, &h2, &m2, &s2, &d);
%02d//%02d的意思是输出二位不足的自动补0;
cin.get();或者 getchar();//滤去上次的回车键
语法:
const char *c_str();
c_str()函数返回一个指向正规C字符串的指针常量, 内容与本string串相同.
这是为了与c语言兼容,在c语言中没有string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串样式。
注意:一定要使用strcpy()函数 等来操作方法c_str()返回的指针
比如:最好不要这样:
char* c;
string s="1234";
c = s.c_str(); //c最后指向的内容是垃圾,因为s对象被析构,其内容被处理,同时,编译器也将报错——将一个const char *赋与一个char *。
应该这样用:
char c[20];
string s="1234";
strcpy(c,s.c_str());
这样才不会出错,c_str()返回的是一个临时指针,不能对其进行操作
再举个例子
c_str() 以 char* 形式传回 string 内含字符串
如果一个函数要求char*参数,可以使用c_str()方法:
string s = "Hello World!";
printf("%s", s.c_str()); //输出 "Hello World!"
算法:
现在学了dfs、bfs、并查集、d算法、动态规划、
第九届总结
- 1048575/524288 //利用等比数列会更快 Sn=a1*(1-qn)/(1-q);
- 5225 //错误 没有搞懂大小年;不同大年是year%40&&year%100!=o 世纪大年是year%4000 这些年的2月都是29天
- 求100个数相乘有多少个0,思路错了不是简单的全部相乘这样势必是超时,应分析相乘的0是如何产生的,通过分析有二种情况:
3.1 尾部有0(相当于含有因子10)
3.2 含有2因子的数和含有5因子的数相乘得到尾部为0
注意二种情况要区分开来 - 使用队列压入,3、5、7(3队头 - 5队头 - 7*队头)注意判重
- 填空,试探法