本博客用于记录2020夏季小学期,CCF备考练习中学习、复习到的算法、数据结构、语法、常用方法等知识的特殊总结。着眼于解决暴露的缺陷,不能够解决全面的知识内容。同时数据结构P2可能暂时停止继续刊载复习。
!!!!
很奇葩 目录后面的 STL 部分显示不全 /哭
!!!!
题目描述:N个人围城一个圈子,最后一个人和第一个人相邻。从1开始报数,每隔M个人就淘汰一个人,问最后留下人的序号
(1)算法分析: 此类循环报数-淘汰问题可以统一使用约瑟夫数学算法,即F(n)=(F(n-1)+M)%N。其中F(1)=0,由此可以递归或者循环求得最后的坐标位数进而求得序号。
递推公式: F(n)=(F(n-1)+m)%n的求解方法
第一轮: m n 从0计数的位置大小中,m-1位置淘汰。由剩余的n-1个人构成n-大小的约瑟夫循环
此时,假设k位置成为首位,所以新的约瑟夫位置m–>0
m+1–>1
m+2–>2
.
.
.
m-2–>n-2
m-1–>n-1
若下一次淘汰对象位置在x位置,则x位置在第一次约瑟夫循环中,其位置是x’ = (x+m)%n
所以由于最后一次由1个大小构成的位置是0,即F(1)=0 F(2)=(0+m)%2 -->F(n)=(F(n-1)+m)%n
(2)算法实现
int main()
{
int m,n; //间隔数 总人数
cin>>n>>m;
int pos=0;
for(int i=1;i<=n;i++)
r =(r+m)%i;
cout<<r+1<<endl;
}
(3)注意事项:由于本算法计算的是位置坐标,而对象又是从1开始计数,所以对应序号应该是所得到的坐标+1(pos+1)
题目:给定三角形的三个顶点的坐标,求解三角形的面积
(1)常用方法
方法一:海伦公式
S:面积 a,b,c:三条边的边长 p:三角形周长的一半
S =sqrt(p*(p-a)(p-b)(p-c))
方法二:向量内积的模
A,B,C为三角形的三个顶点 AB=(x2-x1,y2-y1) AC=(x3-x1,y3-y1) 此时|AB × AC| =|AB|*|AC|sin
AB=(x2-x1,y2-y1) AC=(x3-x1,y3-y1)
S =1/2
方法三:平面解析几何法
S =| x1 y1 1 |
| x2 y2 1 | * 1/2
| x3 y3 1 |
=(x1y2+x2y3+x3y1-x2y1-x3y2-x1y3) * 1/2
注意:一般的面积用double浮点数类型存储即可
数论:正数唯一分解定理: 一个大于1的自然数,要么本身是质数,要么可以分解为两个或以上的质数的幂积。其公式表示为:
数论:约数个数定理: 一个正数的约数可能是质数,也可能是合数。根据质因数分解与Eratosthenes算法,我们能够得到质因数的个数。 同时其合数质因数同样可以表示为有限个质数的幂积。由此可以利用约数个数定理求解一个整数约数的个数。其公式表示为:
数论:约数和定理: 一个大于1的自然数,根据正数唯一分解定理可以分解为其质因数的幂积 A=p1^a1 + p2^a2 + p3^a3 +… 约数和定理可以表示该自然数所有约数的和。其公式表示为:
一般的,我们会使用到上述定理去求解指定数字的约数个数与约数和。
(1)约数个数定理求解约数个数
题目:给定数字N,求其约数的总个数
①要使用约数个数定理,就需要求解其各个质因数的幂。因此我们需要使用到质因数分解的方法。然后在带入公式求解约数个数。注意:当N本身为质数时,其公约数数目为2
②算法实现
int result =1;
int main()
{
for(int k=2;k<=n/k;k++)
{
int p =1; //(1+a1)*....
if(n%k ==0)
{
while(n%k ==0)
{
n /=k;
p++;
}
}
result *=p; //实现连续的乘法
}
if(n >1) result =2; //本身是质数
cout<<result<<endl;
return 0;
}
(2)约数和定理求约数和
①由约数和定理,N =(1+p1^1+ p1^2+ p1^3 +…+p1a1)*…(1+pk1+ pk^2+ …+pk^ak)。首先需要求解所有的质因数以及其乘积幂,所以需要使用质因数分解。最后带入公式,求解约数和。
②算法分析
int num =1;
int main()
{
for(int k=2;k<=n/k;k++) //质因数分解
{
int temp=1; //存储单个(1+p1^1+p1^2...pq^a1)
int prime=0; //质因数
if(n%k ==0)
{
prime =k;
while(n%k ==0)
{
n /=k;
temp =temp*prime+1;
}
}
num *=temp;
}
if(n >1) num *=(1+x); //本身为质数
cout<<num<<endl;
return 0;
}
质数:一个大于1的自然数,除了1和它本身外,不能被其他自然数整除的数。 一般的,我们都会用到质数的判断统计,质数的筛选。
(1)Eratosthenes算法
题目:输入数字n,得到1-n的所有质数
①一般可以用于判断和筛选给定数字范围内的质数。 本方法是将给定数据范围内的数据进行标记,通过标记识别是否为质数。如果按照遍历试除法,我们需要注意遍历再逐一循环判断,这会有很大的时间开销。 因为质数的倍数一定是合数,所以我们可以通过直接对所找到质数的合数进行标记。同时又因为判断一般从2开始,2和3是天然的质数。由此我们就可以实现对所有质数合数的标记。同时为了方便计算与表示,我们将向量或数字从[1]位开始,跳过[0]位。所以一般要注意数组或者向量的大小n–>n+1
②算法实现
vecotr<int> con;
vector<int> prime_number;
int n_prime =0;
int main()
{
int n;
cin>>n;
for(int i=0;i<=n;i++)
con.push_back(0); //所有数字初始化为0 [0,n]
//质数为0 合数为1
for(int i=2;i<=n;i++)
{
if(con[i] ==1) continue; //标记为合数
n_prime++; //质数计数器
prime_number.push_back(i);//质数保存器
for(int j=i;j<=n/i;j++) //从i开始,前面的已经标记过了
{
con[i*j] =1; //质数的倍数标记
}
}
/*如此我们就可以得到全局范围内所有的质数(vectorprime_number)
和范围内的质数个数了!
*/
}
数论-正数的唯一分解定理: 任何大于1的自然数,要么本身是质数,要么可以表示位两个或以上的质数的幂积。
公式 A=p1^k1 * p2^k2 * p3^k3…。
可以知道,当题目中要求得到该数用质数表述的质数的个数和情况、约数的个数与约数和等情况时,我们需要利用唯一分解定理和质因数分解得到相应质因数及其幂次大小。
(2)质因数分解 质因数+次数(+本身质数
题目:输入以大于一的整数n,求解用质数积表示的质数的个数
①根据唯一分解定理,该数得到的质数的乘积情况是唯一的。我们可以从质数2开始分解,如果还能分解就继续分解下去,并记录对应的个数。由于是天生的质数循环,所以我们得到的是质因数。
②算法实现
vector<pair<int,int> > prime_factor_times;
int main()
{
int n;
cin>>n;
for(int i=2;i<=n;i++)
{
if(n%i ==0) //是质因数
{
prime_factor_times.push_back(make_pair(i,0));
//质因数i 次数0
while(n%i ==0)
{
n /=i;
prime_factor_times[i].second++;
}
}
}
if(n >1) prime_factor_times.push_back(make_pair(n,1));
//n本身是质数
//此时prime_factor_times存储着质因数与对应次数
}
(1)在最近的一次练习中,因为对int型的实际取值范围模糊不清,导致最后遗憾失分。所以在这里复习一下整形各情况的取值范围
int | -2^31 ~2^31-1 (或约等于2*10^9) |
---|---|
long long | -2^63 ~2^63-1 (约等于9*10^19) |
一般的在数据大小,在10^12以上的注意此时数据类型设为long long型 而若涉及到指数幂运算,如2的幂运算时。一般需要使用大数乘法。
题目:任意给定一个正整数 N(N≤100),计算 2 的 N 次方的值。
(1)算法分析:将数字保存在较大的数组中,从第一位开始向后延申。其中需要两个变量 位数变量x和进位数变量t
int a[100000000];
int main()
{
int x=1;
a[1] =1; //从1开始保存,方便计数
int N;
cin>>N;
for(int i=0;i<N;i++)
{
int t=0;
for(int j=1;j<=x;j++)
{
a[j] =a[j]*2+t;
t =a[j]/10;
a[j] =a[j]%10;
//进位
if((j ==x)&&(t>0))
{
x++;
}
}
}
for(int i=x;i>=1;i--)
cout<<a[i];
cout<<endl;
}
(1)大数除法
题目:计算A/B,其中A是不超过1000位的整数(A>=0),B是1位正整数。你需要输出商数Q和余数R,使得A = B * Q + R成立
①算法分析: 使用string类型保存输入的数据。 将string保存的数据通过字符->整数的转化保存在向量中当作被除数(注意,不要用stoi函数哈)。 将商也用向量保存。但是可能商向量的头几位元素都是0。这个时候我们可以先保存,得到完整向量后,再删除位于开头的0.
②关键代码实现: string->vector 被除数–>商
int main()
{
//1:保存被除数
string num;
int divider;
cin>>num>>divider;
vecotor<int> dividant;
for(int i=0;i<num.size();i++)
{
int n =(int)(num[i]-48);
dividant.push_back(n);
}
//2:大数除法运算: 除法运算+头0删除
int remainder =0; //余数
vector<int> result;
for(int i=0;i<devidant.size();i++)
{
remainder =remainder*10+dividant[i];
int k =remainder/divider;
remainder =remainder%divider;
result.push_back(k);
} //得到可能含头0商
while(1)
{
if(result[0] ==0)
result.erase(result.begin(),result.end());
else break;
}
//3:大数除法结果输出
for(int i=0;i<result.size();i++)
cout<<result[i]<<' ';
cout<<endl;
cout<<remaider<<endl;
return 0;
}
进制下数字的表示和数字的大小要做好区分。数字的表示是指使用该进制下符合的数据,按照规律表示数字。 而数字得大小一般的使用十进制理解,数字的大小 Sum =Σ(pi* ri) 数字表达中各位数字的大小*对应的位值
(1)将十进制数字用二进制表示(其他进制的修改2为对应进制数即可)
①常见幂次列举:
2^ 4/5/6/7/8/9 | 16/32/64/128/256/512 |
---|---|
2^ 10/20/30/40 | 1 K/M/G/T |
②算法分析:十进制如何转化为其他进制转换? 以转化为二进制为例: 整数部分除2取余,小数部分乘2取整 。 并最终将得到的数字表示向量翻转即可
// 10_to_2
int main()
{
int n;
cin>>n;
vector<int> binary_num;
while(n !=0)
{
binary_num.push_back(n%2);
n /=2;
}
reverse(binary_num.begin(),binary_num.end());
for(auto p:binary_num)
{ cout<<p; }
cout<<endl;
return 0;
}
如何将字符串转换为整数,又如何把整数转换为字符串。这里将各提供两、三种常用方法。
(1)字符串–>整数的两种方法
①stoi() 转换 (C++11)
比如: int sum =stoi(str);
②逐字符转换
思路:遍历整个字符串,将每个字符转换成整数类型在存入向量或者进行运算存入整数。这种方法也是进行大数运算其中的步骤之一
方法一:一般用于大数存储
void string_to_int(string str)
{
//12345
for(int i=0;i<str.size();i++)
{
int n =(int)(str[i]-48);
num.push_back(n); // 1 2 3 4 5
}
}
vector<int> num;
方法二:直接转换成整数
int string_to_int(string str)
{
int result=0;
for(int i=0;i<str.size();i++)
{
int n =(int)(str[i]-48);
result +=n*pow(10,str.size()-1-i);
}
return result;
}
(2)整数–>字符串的三种方法
①to_string()
例如: string str =to_string(n);
②利用sstream转化
string int_to_string(int n)
{
ostringstream stream;
stream<<n;
return stream.str();
}
③逐位转换
string int_to_string(int n)
{
string str="";
while(n !=0)
{
int r =n%10;
str +=(char)(r+48);
}
reverse(str.begin(),str.end());
return str;
}
题目:对给定的数列,求出其平均方差。
(1)运算公式与对应的运算函数
①方差公式
(2)平均方差的运算步骤
①计算数列和:accumulate()函数–>②计算平均值–>③利用公式计算方差pow()
注意:要保证上面三个变量类型尽量一致
(3)向上取正、向下取整函数
①ceil(x): 取得大于等于x的最小整数
②floor(x): 取得小于等于x的最大整数`
//假设数列已经给出 为 vector num
double sum,average,sd =0;
sum =accumulate(num.begin(),num.end());
average =sum*1.0/num.size();
for(int i=0;i<num.size();i++)
sd +=pow(num[i]-average,2)
sd =sd*1.0/num.size();
//向下取整
cout<<floor(sd)<<endl;
在处理大量数据时,有时为了提高IO效率,可以采用一下两种方式。
(1)sync_with_stdio(): 将cout与print绑定在了一块
(2)cin.tie(): 将cin cout绑定在了一块
int main()
{
std::ios::sync_with_stdio(false); //取消输出绑定
std::cin.tie(0); //取消cin cout绑定
}
(1)Vector 插入:
push_back(elem) or push_back(make_pair(e1,e2))
常见的操作模型
vector<string> v1;
vector<pair<string,int>> v2;
for(int i=0;i<n;i++)
{
string str;
cin>>str;
v1.push_back(str);
v2.push_back(make_pair(str,0));
}
注意事项: 没有新开辟空间的vector不能直接使用位引用使 vector[i]=xxx 而是得使用push_back
(2)Vector 删除:
erase() or clear() or pop_back()
①clear() 清空向量函数:
能够清空向量中的所有数据空间。下面代码中 1=100 2=0
int main()
{
vector<int> p;
for(int i=0;i<100;i++)
p.push_back(i);
cout<<"p.size() ="<<p.size()<<endl; //1
p.clear();
cout<<"p.size() ="<<p.size()<<endl; //2
return 0;
}
②erase() 删除指定元素
erase(vector<>::iterator) erase(vector<>::iterator,vector<>::iterator2)
1 删除了从0~2号位三个元素 2 删除了k号位元素
测试代码
int main()
{
int k;
cin>>k;
vector<int> p;
for(int i=0;i<100;i++)
p.push_back(i);
cout<<"p.size() ="<<p.size()<<endl;
//p.erase(p.begin(),p.begin()+2); 1
p.erase(p.begin()+k); //2
cout<<"p.size() ="<<p.size()<<endl;
for(int i=0;i<p.size();i++)
cout<<p[i]<<' ';
cout<<endl;
return 0;
}
③pop_back() 删除末位元素
(1)map排序
①按key排序:
1:当key为常见类型时,map本身的内部结构是按序存储的,故当插入元素后,元素在map中自动按照key值大小升序排序
2:当key为自定义类型时,需要对map的自动排序功能进行重载。
题目:输出相同出生日期的同学的姓名,生日日期按从小到达输出
本题中使用map
注意:通过map迭代器引用的自定义类型的函数时,需要在类的非静态成员函数后加const,表示该成员变量"read only"。否则会产生以下报错信息
error: passing ‘const birthday’ as ‘this’ argument discards qualifiers [-fpermissive]|
struct birthday
{
int month;
int day;
birthday(int m,int n)
{
month =m;
day =n;
}
birthday();
void show() const
{
cout<<month<<' '<<day<<' ';
}
//重载自动排序:自定义排序方法
bool operator< (const birthday& a) const
{
if(month !=a.month) return month <a.month;
return day <a.day;
}
}
②按value排序
1:第一个想法是使用sort()函数进行排序,但是sort()函数仅仅能够对数组、向量等线性结构进行排序,而对map、set这类的关联容器无法直接排序。所以我们可以将map的pair元素赋值到向量中,在向量中进行排序。
2:代码实现
情景假设:map
bool cmp(pair<string,int>& x,pair<string,int>& y)
{
return x.second >y.second;
}
int main()
{
/* ............. 操作 */
map<string,int> m;
vector<pair<string,int> > vec(m.begin(),m.end());
sort(vec.begin(),vec.end(),cmp);
for(auto p:vec)
cout<<p.first<<endl;
return 0;
}
(2)map迭代方法
map与set不能像vector等线性容器使用下标迭代,map and set的实现是由内部的红黑树支持的,因此也会具备自动排序的能力
①迭代器正向迭代
begin() end()
若无多余的排序操作,将会根据姓名的字典序从小到大输出
// map student; 假设map保存学生与其成绩
map<string,int>::iterator l;
for(l =student.begin();l !=student.end();l++)
cout<<l->first<<' '<<l->second<<endl;
②迭代器反向迭代
rbegin() rend()
若无多余的排序操作,将会根据姓名的字典序从大到小输出
//map student; 假设map保存学生与其成绩
map<string,int>::reverse_iterator l;
for(l =student.rbegin();l !=student.rend();l++)
cout<<l->first<<' '<<l->second<<endl;
③auto方式迭代
//map student; 假设map保存学生与其成绩
for(auto p:student)
cout<<p.first<<' '<<p.second<<endl;
(3)map查找元素
使用map时,如果[key]的存在对操作有影响时,首先需要对其存在性进行判定。同时查找也对删除、插入有需要
①count()计数
①对于map变量m,如果需要查找判断是否存在某一key值’key’,可以使用m.count(key)进行个数查找。如果存在会返回1,否则返回0
情景分析: 同学可以在系统上预约座位。in表示预约,out表示取消预约。但是预约过还没取消的不能再次预约,没有预约过的也不能取消预约。 这个问题中就会存在对某同学是否已经预约或者预约过的判断。 如果有同学没有预约过count(key)=0则其可以直接预约成功或取消预约报错。如果同学已经预约过,则需要对其是否还在使用进行判断。所以此时需要使用到count(key)判断。下面将举例部分代码进行来说明
//此处是in预约过程的一部分代码
if(student.count(id) ==0) //从未预约过 第一次预约
{
student[id] =1; //预约中状态
stu[id] =num; //预约位置个数
rest -=num; //剩余位置个数
cout<<"yes"<<endl;//成功预约
continue;
}
else //预约过 (正在 或 已经取消
{
if(student[id] ==1) //正在使用
{
cout<<"no"<<endl; //已经预约不可再次预约
continue;
}
else
{
student[id] =1; //预约中状态
stu[id] =num; //预约位置个数
rest -=num; //剩余位置个数
cout<<"yes"<<endl;//成功预约
continue;
}
}
②find()查找
find()查找map中特定key值,如果找到了返回key值迭代器位置,不然返回map::iterator
// map student 保存学生的姓名以及其成绩
string name;
cin>>name;
map<string,int>::iterator rit;
rit =student.find(name);
if(rit ==map<string,int>::npos)
cout<<"查无此人"<<endl;
else cout<<"找到啦"<<endl;
(4)map删除元素
①erase()迭代器删除
// map student 保存学生的姓名以及其成绩
map<string,int>::iterator rit;
rit =student.find(name);
student.erase(rit);
②erase()具体key值删除
erase(key),删除成功返回1,否则返回0
if(student.erase(name))
cout<<"删除成功"<<endl;
else cout<<"失败啦"<<endl;