一.常用基础知识
1.常用数据范围
int :10位 【-231—231-1】
long long:19位 【-263—263-1】
四舍五入取整
简易方法:(int)(a+0.5)
2.进制转换
(1)十进制转换为d进制数
//sum:十进制数 d:d进制数
int ans[40], num = 0;
//将十进制数sum转化为d进制,结果存放于数组ans,num表示位数
//ans数组中存放的数字为反序,要倒着输出
do{
ans[num++] = sum % d; //除基取余
sum /= d;
}while(sum != 0);
(2)d进制数转换为十进制数
int y=0,temp=1;
while(x!=0){
y+=(x%10)*temp;
x/=10;
temp*=d;
}
若输出格式中包含ABCD…,可以先定义一个char数组存放字符
char temp[13]={'0','1','2','3','4','5','6','7','8','9','A','B','C'};
扩展:
输入字符串形式的数字,将其转换成十进制整数
string s;
getline(cin,s);
int temp=0;
for(int i=0;i<s.size();i++){
temp=temp*10+(s[i]-'0');
}
3.scanf函数与getline函数
scanf函数:读入字符数组时,必须读入字符才算作读入,空格、换行符都不会读入
注意区分scanf("%d %c")
和scanf("%d%d")
前者在输入字符的时候要特别注意,后者一般用在输入数字上
getline函数:会读入一行,不管是空还是有字符
常见用法:
//char数组实现输入字符串
char str[100];
cin.getline(str,100);
//string实现输入字符串
string str;
getline(cin,str);
重点:
(1) 输入字符串尽量使用getline函数,PAT中无法使用gets函数
因此我们在使用时一定要注意,如果有空字符会读入,一定要使用getline
(2) getline函数之前包括其他输入时,先清空输入流
int n;
string str;
cin>>n;
cin.ignore();
getline(cin,str);
4.赋值函数
memset函数 必须加string.h的头文件
memset(a,0,sizeof(a));
fill函数 必须加algorithm的头文件
fill(a,a+n,0);
5.字典序问题
涉及字符串比较、时间大小比较,且数据量小的时候,可采用字符串string的字典序直接比较。
注意使用string类型时,必须加#include <string>
的头文件
补充重点:
字符串比较函数strcmp(,),比较原则依然是字典序
strcmp(str1,str2);
返回值为负整数(str1<str2)、0(str1=str2)、正整数(str1>str2)
当数据量大的时候,可以使用二维char数组直接比较字典序(A1047)
6.各种头文件对应方法
<math.h>
fabs(double x):(浮点数)取绝对值
floor(double x):向下取整
ceil(double x):向上取整
sqrt(double x):算术平方根
round(double x):四舍五入
<algorithm>
max(x,y) / min(x,y):最大值 / 最小值
abs(int x):(整数)取绝对值
swap():交换
reverse():逆转
sort():排序
7.溢出判断
若a,b均为int型变量,(31位)
a>0,b>0,a+b<0 发生正溢出;
a<0,b<0.a+b>0 发生负溢出 。
8.最后一位不能有空格
若输出数组,且最后一个数后不能输出空格,应先用循环输出前n-1个数,最后单独输出最后一个数,或者用辅助数据temp计数判断输出。
9.卡特兰数
用于计算给定n个结点,求二叉树的形态数
二.字符串问题
1.字符串hash
将字符串转换成数字(A1039)
int getid(char a[]){
int sum=0;
for(int i=0;i<3;i++){
sum=sum*26+(a[i]-'A');
}
sum=sum*10+(a[3]-'0');
return sum;
}
2.字符串长度问题
C语言:
#include <string.h>
#include <stdio.h>
char str[100];
gets(str); //尽量不使用gets函数输入字符串
int len=strlen(str);
C++:
#include <iostream>
#include <string>
using namespace std;
string str;
cin>>str;
int len=str.length();
3.字符串string常用函数
auto关键字:自动推断变量类型,可用于表示地址变量
举例
isdigit()方法:用来判断是否为数字
char str[100];
string n="abc123";
for(auto it=n.begin();it!=n.end();it++){
int k=isdigit(*it)?*it-'0':*it-'0'+10;
}
//注意it属于地址变量,若取对应位置字符时,应采用指针表示
max_element()方法:返回最大值的第一个位置
char m=*max_element(n2.begin(),n2.end());
4.字符串输入注意问题
(1)若题目要求先输入n,再输入n个字符串,注意在输入n后吸收回车符
getchar(); //吸收空格
(2)字符串输入一般使用getline
三.动态规划
1.最大连续子序列和
dp数组:dp[i]表示以A[i]为末尾的连续序列的最大和(A[i]必须为末尾)
状态转移方程:
dp[i] = max(A[i],dp[i-1]+A[i]);
2.最长不下降子序列(LIS)
dp数组:dp[i]表示以A[i]为末尾的最长不下降子序列的长度(A[i]必须为末尾)
状态转移方程:
dp[i] = max(1,dp[j]+1);
3.最长公共子序列(LCS)
dp数组:dp[i][j]表示字符串A的i号位和字符串B的j号位之前的LCS长度(下标从1开始)
状态转移方程:
dp[i][j]=dp[i-1][j-1]+1; A[i]==B[j]
dp[i][j]=max(dp[i-1][j],dp[i][j-1]); A[i]!=B[j]
4.注意事项
(1)dp数组以及目标数组都定义在main函数外
(2)LCS输入字符串时,由于下标应从1开始,因此输入格式如下:
char a[100];
cin>>(a+1); 或者 gets(a+1);
四.数学问题
1.素数问题
判断一个数是否为素数
bool isPrime(int n){
if(n<=1)
return false;
for(int i=2;i*i<=n;i++){
if(n%i==0)
return false;
}
return true;
}
2.二分查找
upper_bound(first,last,val):返回第一个大于val的元素位置
int upper_bound(int A[],int left,int right,int x){
while(left<right){
int mid=(left+right)/2;
if(A[mid]>x){ //区别点
right=mid;
}else{
left=mid+1;
}
}
return left;
}
lower_bound(first,last,val):返回第一个大于等于val的元素位置
int lower_bound(int A[],int left,int right,int x){
while(left<right){
int mid=(left+right)/2;
if(A[mid]>=x){ //区别点
right=mid;
}else{
left=mid+1;
}
}
return left;
}
五.排序问题
插入排序与归并排序(A1089)
插入排序与堆排序(A1098)
Tip:堆排序由于使用2i或2i+1表示左右子树,所以for循环应该从1开始,而不是从0开始
1.sort比较的cmp函数
整数比较:
//从大到小
bool cmp(int a,int b){
return a>b;
}
结构体的比较:
//先按x属性从小到大,再按y属性从小到大
bool cmp(Student a,Student b){
if(a.x!=b.x)
return a.x<b.x;
return a.y<b.y;
}
注意点:排序问题中的计算同分排名时,参考A1025的做法
int r=1;
sort(s,s+num,cmp);
for(int i=0;i<num;i++){
if(i>0&&s[i].score!=s[i-1].score)
r=i+1;
printf("%s %d",s[i].id,r);
}
六.C++模板库
1.vector变长数组
若输入n个数且要定义一个n个大小的数组,可采取vector数组的resize方法
#include <iostream>
#include <vector>
using namespace std;
vector<int> v;
int n;
cin>>n;
v.resize(n);
2.Map数组常见问题
若采用空间换时间的辅助数组,注意辅助数组的大小不能过大,否则容易产生段错误
解决办法:使用map数组
3.优先队列(priority_queue)
priority_queue<int> q;
priority_queue<int,vector<int>,less<int>> q; 优先级从大到小
priority_queue<int,vector<int>,greater<int>> q; 优先级从小到大
4.栈的应用(后缀序列求值)
思路:输入一个字符串,如果输入的元素为数字,则入栈,如果输入的元素是运算符,则出栈两个元素s1,s2,将s2运算符s1的结果入栈,最终输出栈顶元素,则为最终求得的结果。
#include <iostream>
#include <stack>
#include <string>
using namespace std;
stack<int> s;
int main(){
string str;
cin>>str;
int k;
for(int i=0;i<str.size();i++){
if(str[i]>='0'&&str[i]<='9'){
int temp=str[i]-'0';
s.push(temp);
}else if(str[i]=='+'){
int a=s.top();
s.pop();
int b=s.top();
s.pop();
k=b+a;
s.push(k);
}else if(str[i]=='-'){
int a=s.top();
s.pop();
int b=s.top();
s.pop();
k=b-a;
s.push(k);
}
}
cout<<s.top()<<endl;
return 0;
}
七.数据结构
1.静态链表做题思路
1.结构体定义
struct Node{
int address;
typename data;
int next;
xxx; //根据题目要求确定
}node[maxn];
2.静态链表初始化
for(int i=0;i<maxn;i++){
node[i].xxx=0;
}
3.串联链表
法一:一次scanf
for(int i=0;i<n;i++){
scanf("%d %c %d",&address,&data,&next);
node[address].data=data;
node[address].next=next;
}
法二:两次scanf
for(int i=0;i<n;i++){
scanf("%d",&address);
scanf("%d%d",&node[address].data,&node[address].next);
node[address].address=address;
}
4.遍历链表
int s;
for(int p=s;p!=-1;p=node[p].next){
node[p].xxx;
}
Tip:
1.结构体定义的变量可以和main函数定义的变量名相同
2.注意地址的输出可以采用“%05d”
2.二叉树遍历
先序遍历/中序遍历/后序遍历
注意:先写递归边界
if(root=NULL){
return;
}
层序遍历(队列实现)
struct node{
int data;
node* lchild;
node* rchild;
int layer; //记录二叉树所在层数
}
int num=0;
void LayerOrder(node* root){
queue<node*> q;
root->layer=1;
q.push(root);
while(!q.empty()){
node* now=q.front();
q.pop();
printf("%d",now->data);
num++;
if(num<n)
printf(" ");
if(now->lchild!=NULL){
now->lchild->layer=now->layer+1;
q.push(now->lchild);
}
if(now->rchild!=NULL){
now->rchild->layer=now->layer+1;
q.push(now->rchild);
}
}
}
Tip:如果最后输出最后一个数不能为空格,可以设置一个标记数num记录,在递归左右子树之前加一个判断语句
3.DFS与BFS
DFS:系统栈实现
BFS:队列实现
重点例题:A1004
4.BST(二叉查找树)
插入操作
void insert(node* &root,int data){ //BST的插入操作
if(root==NULL){
root=new node;
root->data=data;
root->lchild=root->rchild=NULL;
return;
}
if(data<root->data)
insert(root->lchild,data);
else
insert(root->rchild,data);
}
遍历
注意:用vector数组存储遍历序列
void preOrder(node* root,vector<int>&vi){ //先序遍历
if(root==NULL)
return;
vi.push_back(root->data);
preOrder(root->lchild,vi);
preOrder(root->rchild,vi);
}
5.图
(1)无向图若增加边数让该无向图图连通,其中增加的边数=连通块个数-1
(2)连通块个数的求法:①图的遍历(A1013) ②并查集
6.迪杰斯特拉算法(Dijkstra)
Dijkstra完整代码:
//邻接表法
vector<int> pre[maxv];
int n,G[maxv][maxv],d[maxv];
bool vis[maxv]={false};
void Dijkstra(int s){
fill(d,d+maxv,INF);
d[s]=0;
for(int i=0;i<n;i++){
int u=-1,min=INF;
for(int j=0;j<n;j++){
if(vis[j]==false&&d[j]<min){
u=j;
min=d[j];
}
}
if(u==-1)
return;
vis[u]=true;
for(int v=0;v<n;v++){
if(vis[v]==false&&G[u][v]!=INF){
if(d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
pre[v].clear();
pre[v].push_back(v); //区别点
}else if(d[u]+G[u][v]==d[v])
pre[v].push_back(v);
}
}
}
}
7.最短路径问题
解决方法:Dijkstra+DFS(A1030)
注意事项:
(1)递归边界
void DFS(int v){
if(v==st){ //st为起点
printf("%d ",v);
return;
}
DFS(pre[v]);
printf("%d ",v);
}
(2)输入初始参数(起点,终点,权值1,权值2)
int u,v;
for(int i=0;i<n;i++){
printf("%d%d",&u,&v);
printf("%d%d",&G[u][v],&cost[u][v]);
} //注意分开两个printf输入