2020年暑假算法笔记
2020年7月11日:
- map 相关用法
Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下map内部数据的组织,map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,后边我们会见识到有序的好处。
1、map简介
map是一类关联式容器。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。
对于迭代器来说,可以修改实值,而不能修改key。
2、map的功能
自动建立Key - value的对应。key 和 value可以是任意你需要的类型。
根据key值快速查找记录,查找的复杂度基本是Log(N),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次。
快速插入Key -Value 记录。
快速删除记录
根据Key 修改value记录。
遍历所有记录。
3、使用map
使用map得包含map类所在的头文件
#include
map对象是模板类,需要关键字和存储对象两个模板参数:
std:map<int,string> personnel;
这样就定义了一个用int作为索引,并拥有相关联的指向string的指针.
为了使用方便,可以对模板类进行一下类型定义,
typedef map<int,CString> UDT_MAP_INT_CSTRING;
UDT_MAP_INT_CSTRING enumMap;
4、 map的构造函数
map共提供了6个构造函数,这块涉及到内存分配器这些东西,略过不表,在下面我们将接触到一些map的构造方法,这里要说下的就是,我们通常用如下方法构造一个map:
map<int, string> mapStudent;
5、 数据的插入
在构造map容器后,我们就可以往里面插入数据了。这里讲三种插入数据的方法:
第一种:用insert函数插入pair数据,下面举例说明(以下代码虽然是随手写的,应该可以在VC和GCC下编译通过,大家可以运行下看什么效果,在VC下请加入这条语句,屏蔽4786警告 #pragma warning (disable:4786) )
//数据的插入–第一种:用insert函数插入pair数据
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
第二种:用insert函数插入value_type数据,下面举例说明
[cpp] view plain copy
//第二种:用insert函数插入value_type数据,下面举例说明
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(map<int, string>::value_type (1, "student_one"));
mapStudent.insert(map<int, string>::value_type (2, "student_two"));
mapStudent.insert(map<int, string>::value_type (3, "student_three"));
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
第三种:用数组方式插入数据,下面举例说明
[cpp] view plain copy
//第三种:用数组方式插入数据,下面举例说明
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent[1] = "student_one";
mapStudent[2] = "student_two";
mapStudent[3] = "student_three";
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
以上三种用法,虽然都可以实现数据的插入,但是它们是有区别的,当然了第一种和第二种在效果上是完成一样的,用insert函数插入数据,在数据的 插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,但是用数组方式就不同了,它可以覆盖以前该关键字对 应的值,用程序说明
mapStudent.insert(map<int, string>::value_type (1, “student_one”));
mapStudent.insert(map<int, string>::value_type (1, “student_two”));
上面这两条语句执行后,map中1这个关键字对应的值是“student_one”,第二条语句并没有生效,那么这就涉及到我们怎么知道insert语句是否插入成功的问题了,可以用pair来获得是否插入成功,程序如下
pair<map<int, string>::iterator, bool> Insert_Pair;
Insert_Pair = mapStudent.insert(map<int, string>::value_type (1, “student_one”));
我们通过pair的第二个变量来知道是否插入成功,它的第一个变量返回的是一个map的迭代器,如果插入成功的话Insert_Pair.second应该是true的,否则为false。
下面给出完成代码,演示插入成功与否问题
[cpp] view plain copy
//验证插入函数的作用效果
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
pair<map<int, string>::iterator, bool> Insert_Pair;
Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_one"));
if(Insert_Pair.second == true)
cout<<"Insert Successfully"<<endl;
else
cout<<"Insert Failure"<<endl;
Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_two"));
if(Insert_Pair.second == true)
cout<<"Insert Successfully"<<endl;
else
cout<<"Insert Failure"<<endl;
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
大家可以用如下程序,看下用数组插入在数据覆盖上的效果
[cpp] view plain copy
//验证数组形式插入数据的效果
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent[1] = "student_one";
mapStudent[1] = "student_two";
mapStudent[2] = "student_three";
map<int, string>::iterator iter;
for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout<<iter->first<<' '<<iter->second<<endl;
}
6、 map的大小
在往map里面插入了数据,我们怎么知道当前已经插入了多少数据呢,可以用size函数,用法如下:
Int nSize = mapStudent.size();
7、 数据的遍历
这里也提供三种方法,对map进行遍历
第一种:应用前向迭代器,上面举例程序中到处都是了,略过不表
第二种:应用反相迭代器,下面举例说明,要体会效果,请自个动手运行程序
[cpp] view plain copy
//第二种,利用反向迭代器
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::reverse_iterator iter;
for(iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
cout<<iter->first<<" "<<iter->second<<endl;
}
第三种,用数组的形式,程序说明如下:
[cpp] view plain copy
//第三种:用数组方式,程序说明如下
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
int nSize = mapStudent.size();
//此处应注意,应该是 for(int nindex = 1; nindex <= nSize; nindex++)
//而不是 for(int nindex = 0; nindex < nSize; nindex++)
for(int nindex = 1; nindex <= nSize; nindex++)
cout<<mapStudent[nindex]<<endl;
}
8、 查找并获取map中的元素(包括判定这个关键字是否在map中出现)
在这里我们将体会,map在数据插入时保证有序的好处。
要判定一个数据(关键字)是否在map中出现的方法比较多,这里标题虽然是数据的查找,在这里将穿插着大量的map基本用法。
这里给出三种数据查找方法
第一种:用count函数来判定关键字是否出现,其缺点是无法定位数据出现位置,由于map的特性,一对一的映射关系,就决定了count函数的返回值只有两个,要么是0,要么是1,出现的情况,当然是返回1了
第二种:用find函数来定位数据出现位置,它返回的一个迭代器,当数据出现时,它返回数据所在位置的迭代器,如果map中没有要查找的数据,它返回的迭代器等于end函数返回的迭代器。
查找map中是否包含某个关键字条目用find()方法,传入的参数是要查找的key,在这里需要提到的是begin()和end()两个成员,
分别代表map对象中第一个条目和最后一个条目,这两个数据的类型是iterator.
程序说明
[cpp] view plain copy
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::iterator iter;
iter = mapStudent.find(1);
if(iter != mapStudent.end())
cout<<"Find, the value is "<<iter->second<<endl;
else
cout<<"Do not Find"<<endl;
return 0;
}
通过map对象的方法获取的iterator数据类型是一个std::pair对象,包括两个数据 iterator->first和 iterator->second分别代表关键字和存储的数据。
第三种:这个方法用来判定数据是否出现,是显得笨了点,但是,我打算在这里讲解
lower_bound函数用法,这个函数用来返回要查找关键字的下界(是一个迭代器)
upper_bound函数用法,这个函数用来返回要查找关键字的上界(是一个迭代器)
例如:map中已经插入了1,2,3,4的话,如果lower_bound(2)的话,返回的2,而upper-bound(2)的话,返回的就是3
Equal_range函数返回一个pair,pair里面第一个变量是Lower_bound返回的迭代器,pair里面第二个迭代器是Upper_bound返回的迭代器,如果这两个迭代器相等的话,则说明map中不出现这个关键字,
程序说明
[cpp] view plain copy
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent[1] = "student_one";
mapStudent[3] = "student_three";
mapStudent[5] = "student_five";
map<int, string>::iterator iter;
iter = mapStudent.lower_bound(1);
//返回的是下界1的迭代器
cout<<iter->second<<endl;
iter = mapStudent.lower_bound(2);
//返回的是下界3的迭代器
cout<<iter->second<<endl;
iter = mapStudent.lower_bound(3);
//返回的是下界3的迭代器
cout<<iter->second<<endl;
iter = mapStudent.upper_bound(2);
//返回的是上界3的迭代器
cout<<iter->second<<endl;
iter = mapStudent.upper_bound(3);
//返回的是上界5的迭代器
cout<<iter->second<<endl;
pair<map<int, string>::iterator, map<int, string>::iterator> mappair;
mappair = mapStudent.equal_range(2);
if(mappair.first == mappair.second)
cout<<"Do not Find"<<endl;
else
cout<<"Find"<<endl;
mappair = mapStudent.equal_range(3);
if(mappair.first == mappair.second)
cout<<"Do not Find"<<endl;
else
cout<<"Find"<<endl;
return 0;
}
9、 从map中删除元素
移除某个map中某个条目用erase()
该成员方法的定义如下:
iterator erase(iterator it);//通过一个条目对象删除
iterator erase(iterator first,iterator last)//删除一个范围
size_type erase(const Key&key);//通过关键字删除
clear()就相当于enumMap.erase(enumMap.begin(),enumMap.end());
这里要用到erase函数,它有三个重载了的函数,下面在例子中详细说明它们的用法
[cpp] view plain copy
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
//如果你要演示输出效果,请选择以下的一种,你看到的效果会比较好
//如果要删除1,用迭代器删除
map<int, string>::iterator iter;
iter = mapStudent.find(1);
mapStudent.erase(iter);
//如果要删除1,用关键字删除
int n = mapStudent.erase(1);//如果删除了会返回1,否则返回0
//用迭代器,成片的删除
//一下代码把整个map清空
mapStudent.erase( mapStudent.begin(), mapStudent.end() );
//成片删除要注意的是,也是STL的特性,删除区间是一个前闭后开的集合
//自个加上遍历代码,打印输出吧
}
10、 map中的swap用法
map中的swap不是一个容器中的元素交换,而是两个容器所有元素的交换。
11、 排序 · map中的sort问题
map中的元素是自动按Key升序排序,所以不能对map用sort函数;
这里要讲的是一点比较高深的用法了,排序问题,STL中默认是采用小于号来排序的,以上代码在排序上是不存在任何问题的,因为上面的关键字是int 型,它本身支持小于号运算,在一些特殊情况,比如关键字是一个结构体,涉及到排序就会出现问题,因为它没有小于号操作,insert等函数在编译的时候过 不去,下面给出两个方法解决这个问题。
第一种:小于号重载,程序举例。
[cpp] view plain copy
#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef struct tagStudentinfo
{
int niD;
string strName;
bool operator < (tagStudentinfo const& _A) const
{ //这个函数指定排序策略,按niD排序,如果niD相等的话,按strName排序
if(niD < _A.niD) return true;
if(niD == _A.niD)
return strName.compare(_A.strName) < 0;
return false;
}
}Studentinfo, *PStudentinfo; //学生信息
int main()
{
int nSize; //用学生信息映射分数
map<Studentinfo, int>mapStudent;
map<Studentinfo, int>::iterator iter;
Studentinfo studentinfo;
studentinfo.niD = 1;
studentinfo.strName = "student_one";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 90));
studentinfo.niD = 2;
studentinfo.strName = "student_two";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 80));
for (iter=mapStudent.begin(); iter!=mapStudent.end(); iter++)
cout<<iter->first.niD<<' '<<iter->first.strName<<' '<<iter->second<<endl;
return 0;
}
第二种:仿函数的应用,这个时候结构体中没有直接的小于号重载,程序说明
[cpp] view plain copy
//第二种:仿函数的应用,这个时候结构体中没有直接的小于号重载,程序说明
#include <iostream>
#include <map>
#include <string>
using namespace std;
typedef struct tagStudentinfo
{
int niD;
string strName;
}Studentinfo, *PStudentinfo; //学生信息
class sort
{
public:
bool operator() (Studentinfo const &_A, Studentinfo const &_B) const
{
if(_A.niD < _B.niD)
return true;
if(_A.niD == _B.niD)
return _A.strName.compare(_B.strName) < 0;
return false;
}
};
int main()
{ //用学生信息映射分数
map<Studentinfo, int, sort>mapStudent;
map<Studentinfo, int>::iterator iter;
Studentinfo studentinfo;
studentinfo.niD = 1;
studentinfo.strName = "student_one";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 90));
studentinfo.niD = 2;
studentinfo.strName = "student_two";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 80));
for (iter=mapStudent.begin(); iter!=mapStudent.end(); iter++)
cout<<iter->first.niD<<' '<<iter->first.strName<<' '<<iter->second<<endl;
}
由于STL是一个统一的整体,map的很多用法都和STL中其它的东西结合在一起,比如在排序上,这里默认用的是小于号,即less<>,如果要从大到小排序呢,这里涉及到的东西很多,在此无法一一加以说明。
还要说明的是,map中由于它内部有序,由红黑树保证,因此很多函数执行的时间复杂度都是log2N的,如果用map函数可以实现的功能,而STL Algorithm也可以完成该功能,建议用map自带函数,效率高一些。
下面说下,map在空间上的特性,否则,估计你用起来会有时候表现的比较郁闷,由于map的每个数据对应红黑树上的一个节点,这个节点在不保存你的 数据时,是占用16个字节的,一个父节点指针,左右孩子指针,还有一个枚举值(标示红黑的,相当于平衡二叉树中的平衡因子),我想大家应该知道,这些地方 很费内存了吧,不说了……
12、
map的基本操作函数:
C++ maps是一种关联式容器,包含“关键字/值”对
begin() 返回指向map头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数
empty() 如果map为空则返回true
end() 返回指向map末尾的迭代器
equal_range() 返回特殊条目的迭代器对
erase() 删除一个元素
find() 查找一个元素
get_allocator() 返回map的配置器
insert() 插入元素
key_comp() 返回比较元素key的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向map尾部的逆向迭代器
rend() 返回一个指向map头部的逆向迭代器
size() 返回map中元素的个数
swap() 交换两个map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素value的函数
- 关于取模的问题有规律可寻
例如:f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7.求f(n)
这题的关键就在于时间,因为n可能很大很大.但因为有mod 7,所以f(n)取值是0-6,共7个取值,而f(n)又由f(n-1) 和 f(n-2)两个决定,因此最多有7*7=49种可能的组合,因此在50以内必然出现循环,所以我们用数组模拟前49组.
i从0开始就是48,从1开始就是49.这里是i从1开始,如果n=50,就相当于n=1,所以后面的n要%49,因为避免超时 - 大数加法
利用字符串模拟加法
#include <iostream>
#include <cstdio>
#include <cstring>
#include<algorithm>
#include <cstdio>
#include<queue>
#include<string>
#include <bits/stdc++.h>
using namespace std;
string add(string a,string b)
{
a=a.substr(a.find_first_not_of('0'));//暂时不清楚有何用处,但不能少了
b=b.substr(a.find_first_not_of('0'));
int lenA=a.length();
int lenB=b.length();
int lenC=max(lenA,lenB)+10;
reverse(a.begin(),a.end());
reverse(b.begin(),b.end());
string c(lenC,'0');
for(int i=0;i<lenA;i++)
c[i]=a[i];
int temp=0;
for(int i=0;i<=lenC;i++)
{
if(i<lenB)
{
temp+=(c[i]-'0')+(b[i]-'0');
}
else
temp+=(c[i]-'0');
c[i]=(temp%10)+'0';
temp/=10;
}
reverse(c.begin(),c.end());
return c.substr(c.find_first_not_of('0'));;
}
int main()
{
string a;
string b;
int t=1;int ans=0;
cin>>t;
while(t--)
{
ans++;
cin>>a;
cin>>b;
string c=add(a,b);
printf("Case %d:\n",ans);
cout<<a<<" + "<<b<<" = "<<c<<endl;
if(t!=0)
cout<<endl;
}
return 0;
}
2020年7月12日:
- 大数减法
#include <bits/stdc++.h>
#include <algorithm>
#include <string>
using namespace std;
int flag=1;//1表示的是正数,0表示的是负数
//大数减法
bool mystrcmp(string a,string b)//比较两字符串的大小,a<b返回true
{
if(a.length()<b.length())
return true;
if(a.length()>b.length())
return false;
return a>=b?false:true;
}
string subtract(string a,string b)//完整的大数减法
{
string x,y;//x记录大字符串
if(a==b)
return "0";
if(mystrcmp(a,b))//意味着是a<b,结果是负数,并交换a,b的值
{
flag=0;
x=b;
y=a;
}
else
{
x=a;
y=b;
}
reverse(x.begin(),x.end());
reverse(y.begin(),y.end());
string ans=x;
for(int i=0;i<y.length();i++)
{
if(x[i]>=y[i])
{
ans[i]=(x[i]-y[i])+'0';
}
else//不够减,就要借位
{
int k=1;
while(x[i+k]==0)
{
x[i+k]=9;
k++;
}
x[i+k]=(x[i+k]-'1')+'0';
ans[i]=((x[i]-'0'+10)-(y[i]-'0'))+'0';
}
}
reverse(ans.begin(),ans.end());
return ans.substr(ans.find_first_not_of('0'));
}
int main()
{
string a;
string b;
while(cin>>a>>b)
{
flag=1;
string ans=subtract(a,b);
if(flag==0)
cout<<'-';
cout<<ans<<endl;
}
return 0;
}
2020年7月13日:
- 素数的个数(埃氏算法)
#include <bits/stdc++.h>
#include <algorithm>
#include <string>
using namespace std;
//素数判定(只能被1或自身整除的数为素数)
//给一个区间解出素数有多少个,也称埃氏算法,复杂度为O(n)可以这么认为
int prime[1000000];int is_prime[1000000];
int sieve(int n)
{
int p=0;
for(int i=0;i<=n;i++)
is_prime[i]=1;
is_prime[1]=is_prime[0]=0;
for(int i=2;i<=n;i++)
{
if(is_prime[i])
{
prime[p++]=i;
for(int j=2*i;j<=n;j+=i)
is_prime[j]=0;
}
}
return p;
}
int main()
{
int n;
cin>>n;
int ans=sieve(n);
cout<<ans<<endl;
for(int i=0;i<ans;i++)
printf("%d ",prime[i]);
cout<<endl;
return 0;
}
区间内素数的个数可以用后者的素数的个数减去前者的素数个数留下的就是答案,如果要求打印具体素数,则可以用在后者的prime中从相同的个数下标开始打印到结尾
-
模运算
(a + b) % p = (a % p + b % p) % p (1)(a – b) % p = (a % p – b % p) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
(a^b) % p = ((a % p)^b) % p (4)
-
快速幂运算
#include <bits/stdc++.h>
#include <algorithm>
#include <string>
using namespace std;
//快速幂运算
int main()
{
long long n,x;
long long mod;
long long res=1;
cin>>n>>x>>mod;
while(n>0)
{
if(n & 1)
res=res*x % mod;
x=x*x%mod;
n>>=1;
}
cout<<res<<endl;
return 0;
}
7月15日
- lower_bound
lower_bound(dp,dp+n,k):找到比k大的最小的指针
*lower_bound(dp,dp+n,k)=a[i];
7月16日
记忆化搜索=:dfs+记忆
基本框架与DFS 一致, 搜索中保存子问题的值, 在搜索过程中若子问题的最优值已存在, 直接使用而避免重复搜索。
//
// 蓝桥杯题库 真题 地宫寻宝
// 记忆化搜索
#include <cstdio>
#include <cstring>
typedef long long LL;
using namespace std;
const int maxn = 51;
const LL Q = 1000000007; // 基数
int K, N, M; // 个数, 行数, 列数
int G[maxn][maxn];
// 记忆化搜索
LL dp[maxn][maxn][maxn][maxn];
// 方向变更
int xx[2] = {1, 0};
int yy[2] = {0, 1};
// 记忆化搜索, 因为物品价值可能为0, 所以 maxValue 初始值为 -1, maxValue + 1 就是基于此
LL dfsMem(int x, int y, int num, int maxValue){
if (dp[x][y][num][maxValue + 1] != -1){ // 已有结果
return dp[x][y][num][maxValue + 1];
}
if (x == N-1 && y == M-1){
if (num == K || (num == K-1 && G[x][y] > maxValue)){
return dp[x][y][num][maxValue + 1] = 1; // 记录
}
}
LL s = 0;
for (int i = 0; i < 2; i++){ // 遍历下方和右方
int cx = x + xx[i], cy = y + yy[i];
if (cx < N && cy < M){
if (maxValue < G[x][y] && num < K)
s += dfsMem(cx, cy, num+1, G[x][y]) % Q;
s += dfsMem(cx, cy, num, maxValue) % Q;
}
}
return dp[x][y][num][maxValue + 1] = s % Q;
}
int main(){
scanf("%d%d%d", &N, &M, &K);
for (int i = 0; i < N; i++){
for (int j = 0; j < M; j++)
scanf("%d", &G[i][j]);
}
memset(dp, -1, sizeof(dp));
dfsMem(0, 0, 0, -1);
printf("%lld", dp[0][0][0][0]);
return 0;
}
常用技巧精选
尺取法
尺取法:顾名思义,像尺子一样取一段,借用挑战书上面的话说,尺取法通常是对数
组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左
右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效
率高很多,尤其是数据量大的时候,所以尺取法是一种高效的枚举区间的方法,一般
用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地
方,有些情况下尺取法不可行,无法得出正确答案。
使用尺取法时应清楚以下四点:
1、 什么情况下能使用尺取法? 2、何时推进区间的端点? 3、如何推进区间的端
点? 3、何时结束区间的枚举?
尺取法通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情
况,通俗地说,在对所选取区间进行判断之后,我们可
以明确如何进一步有方向地推进区间端点以求解满足条件的区间,如果已经判断了目
前所选取的区间,但却无法确定所要求解的区间如何进
一步得到根据其端点得到,那么尺取法便是不可行的。首先,明确题目所需要求解的
量之后,区间左右端点一般从最整个数组的起点开始,
之后判断区间是否符合条件在根据实际情况变化区间的端点求解答案。
7月17日
回溯:
很多人认为回溯和递归是一样的,其实不然。在回溯法中可以看到有递归的身影,但是两者是有区别的。
回溯法从问题本身出发,寻找可能实现的所有情况。和穷举法的思想相近,不同在于穷举法是将所有的情况都列举出来以后再一一筛选,而回溯法在列举过程如果发现当前情况根本不可能存在,就停止后续的所有工作,返回上一步进行新的尝试。
递归是从问题的结果出发,例如求 n!,要想知道 n!的结果,就需要知道 n*(n-1)! 的结果,而要想知道 (n-1)! 结果,就需要提前知道 (n-1)*(n-2)!。这样不断地向自己提问,不断地调用自己的思想就是递归。回溯和递归唯一的联系就是,回溯法可以用递归思想实现。
#include <stdio.h>
int Queenes[8]={0},Counts=0;
int Check(int line,int list){
//遍历该行之前的所有行
for (int index=0; index<line; index++) {
//挨个取出前面行中皇后所在位置的列坐标
int data=Queenes[index];
//如果在同一列,该位置不能放
if (list==data) {
return 0;
}
//如果当前位置的斜上方有皇后,在一条斜线上,也不行
if ((index+data)==(line+list)) {
return 0;
}
//如果当前位置的斜下方有皇后,在一条斜线上,也不行
if ((index-data)==(line-list)) {
return 0;
}
}
//如果以上情况都不是,当前位置就可以放皇后
return 1;
}
//输出语句
void print()
{
for (int line = 0; line < 8; line++)
{
int list;
for (list = 0; list < Queenes[line]; list++)
printf("0");
printf("#");
for (list = Queenes[line] + 1; list < 8; list++){
printf("0");
}
printf("\n");
}
printf("================\n");
}
void eight_queen(int line){
//在数组中为0-7列
for (int list=0; list<8; list++) {
//对于固定的行列,检查是否和之前的皇后位置冲突
if (Check(line, list)) {
//不冲突,以行为下标的数组位置记录列数
Queenes[line]=list;
//如果最后一样也不冲突,证明为一个正确的摆法
if (line==7) {
//统计摆法的Counts加1
Counts++;
//输出这个摆法
print();
//每次成功,都要将数组重归为0
Queenes[line]=0;
return;
}
//继续判断下一样皇后的摆法,递归
eight_queen(line+1);
//不管成功失败,该位置都要重新归0,以便重复使用。
Queenes[line]=0;
}
}
}
int main() {
//调用回溯函数,参数0表示从棋盘的第一行开始判断
eight_queen(0);
printf("摆放的方式有%d种",Counts);
return 0;
}
7月19日:
- 完全背包问题
dp数组含义:dp[i][j] dp[i][j]dp[i][j]=从编号为1−i 1-i1−i的物品中挑选物品放入容量为j jj的背包中能得到的最大价值。注意:n种物品编号范围为1-n,0做作递推的起点。
初始条件:dp[0][0−W]=0 dp[0][0-W]=0dp[0][0−W]=0
递推公式:
dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j} dp[i][j]=max{dp[i-1][j-kw[i]]+kv[i]|0≤k&kw[i]≤j}
dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k&k∗w[i]≤j}
结果:结果在dp[n][W] dp[n][W]dp[n][W]中
总之其递推公式为: dp[i][j]=max(dp[i][j],dp[i-1][j-kw[i]]+k*v[i]);
#include <iostream>
#include <string.h>
using namespace std;
#define Max_n 105
#define Max_w 10005
//为优化后的代码,其效率较高
int n,W;
int w[Max_n],v[Max_n];
int dp_reuse[Max_w];
void solve_5()
{
//初始化
memset(dp_reuse,0,sizeof(dp_reuse));
for(int i=1; i<=n; i++)
for(int j=w[i]; j<=W; j++)
dp_reuse[j]=max(dp_reuse[j],dp_reuse[j-w[i]]+v[i]);
}
7月23日:
- 归并排序
#include <bits/stdc++.h>
#include <algorithm>
#include <string>
using namespace std;
int n;
void mergesort(int a[],int l,int mid,int r)//处理归并
{
int aux[r-l+1];
for(int k=l;k<=r;k++)
aux[k-l]=a[k];
int i=l;
int j=mid+1;int k;
for(k=l;k<=r;k++)
{
if(i>mid)
{
a[k]=aux[j-l];
j++;
}
else if(j>r)
{
a[k]=aux[i-l];
i++;
}
else if(aux[i]>aux[j-l])
{
a[k]=aux[j-l];
j++;
}
else
{
a[k]=aux[i-l];
i++;
}
}
}
void merge_sort(int a[],int l,int r)
{
if(l>=r)
return ;
int mid=(l+r)/2;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
mergesort(a,l,mid,r);
}
int main()
{
cin>>n;
int a[100000];
for(int i=0;i<n;i++)
cin>>a[i];
merge_sort(a,0,n-1);//归并排序
for(int i=0;i<10;i++)
printf("%d ",a[i]);
}
-
树状数组
树状数组是对一个数组改变某个元素 和 求和比较实用的数据结构。两中操作都是O(logn)传统数组(共n个元素)的元素修改和连续元素求和的复杂度分别为O(1)和O(n)。树状数组通
过将线性结构转换成伪树状结构(线性结构只能逐个扫描元素,而树状结构可以实现跳跃
式扫描),使得修改和求和复杂度均为O(lgn),大大提高了整体效率。给定序列(数列)A,我们设一个数组C满足
C[i] = A[i–2^k+ 1] + … + A[i]
其中,k为i在二进制下末尾0的个数,i从1开始算!
则我们称C为树状数组。
7月27日:
-
str类函数
strcpy() 将目标字符串复制给源字符串并覆盖源字符串
strncpy() 按照指定字符个数copy字符串
strlen() 求字符串的长度
strcmp() 判断两个字符串的是否相等
strcat() 将两个字符串拼接 -
%*s的作用
取决于在scanf中使用还是在printf中使用。
在scanf中使用,则添加了*的部分会被忽略,不会被参数获取。
例如:int a,b;char b[10];scanf("%d%*s",&a,b);输入为:12 abc那么12将会读取到变量a中,但是后面的abc将在读取之后抛弃,不赋予任何变量(例如这里的字符数组b)
在printf中使用,表示用后面的形参替代的位置,实现动态格式输出。
例如:
printf("%*s", 10, s); /意思是输出字符串s,但至少占10个位置,不足的在字符串s左边补空格,这里等同于printf("%10s", s);
7月28日:
- next_permutation
全排列函数,枚举所有的情况
例如:a[]={0,1,2,3,4,5,6,7,8,9}
next_permutation(a,a+10);