基本知识
如果数组大小较大,需要定义在主函数外面,否则会异常退出。因为函数内部申请的局部变量来自于系统栈,允许申请的空间较小。函数外部申请的全局变量来自静态存储区。
memset按字节赋值(头文件string.h),所以最好初始化0或-1. 若需要赋值其他,用fill函数(memset快
int a[5];
memset(a,0,sizeof(a)); //全赋值为0
字符数组可以通过直接赋值字符串来初始化(仅限于初始化)
char str[15]="Good Story!";
%s识别空格作为字符串的结尾,所以到空格之后,scanf不能继续接收,scanf在使用%s时,后面对应数组名前面不用加&获取地址。
gets识别换行符\0作为输入结束。因此如果scanf完一个整数之后要使用gets,需要先用getchar接收整数后换行符。
gets(str1);
gets(str2);
不可输入:
Dear Mozart //用空格是错的,必须换行
可输入:
Dear
Mozart
若不是使用scanf的%s格式或者gets函数输入字符串(这两个可以结尾自动补\0),例如使用getchar,一定要在输入的每个字符串后面加"\0",否则printf和puts输出字符串会乱码
函数: strlen(), strcmp()
strcpy(str1,str2) 把str2复制给str1
strcat(str1,str2)把一个字符串接到另一个字符串后面
sscanf()和sprintf()
sscanf()是把字符数组中的内容以一定格式传到另一个变量。
如 sscanf(str,“%d”,&n); //把字符数组str中的内容以%d的格式写到n中。
int n;
char str[10]="123";
sscanf(str,"%d",&n);
printf("%d",n);
输出: 123
sprintf(str, “%d”,n)作用是把n以%d的格式写到str字符数组中
int n = 233;
int str[10];
sprintf(str,"%d",n);
printf("%s\n",str);
输出:233
用sscanf将str中的内容按格式分别写入n,db,str2:
int n;
float db;
char str[100]="2048:3.14,hello";
char str2[10];
sscanf(str,"%d:%f,%s",&n,&db,str2);
printf("n=%d,db=%.2f,str2=%s",n,db,str2);
输出:
n=2048,db=3.14,str2=hello
sprintf也可如上复杂,将多个变量内容按格式输到一个字符数组。
参数中数组的第一维不需要填写长度(二维数组要填第二维长度),数组作为参数时,在函数中对数组元素的修改就等同于对原数组元素的修改 。
数组可以作为参数,但不允许作为返回类型,如果要返回数组,只能用作为参数去修改(上述方式)
C++ 引用 对引用变量的操作就是对原变量的操作
void change(int&x){
x=1;
}
int main(){
int x=10;
change(x); //也可用其他变量
printf("%d",x);
return 0;
}
传入地址交换两个变量:
void swap(int* &p1,int* &p2){
int* temp;
temp=p1;
p1=p2;
p2=temp;
}
int main(){
int a=1,b=2;
int *p1=&a, *p2=&b;
swap(p1,p2);
printf("a=%d,b=%d",*p1,*p2);
return 0;
}
访问结构体中的元素
struct studentInfo{
int id;
char name[20];
studentInfo* next;
}stu, *p;
//访问stu中变量的写法:
stu.id
stu.name
stu.next
//访问指针变量p中元素:
(*p).id
(*p).name
(*p).next
//访问结构体指针变量内元素还可写为:
p->id
p->name
p->next
构造函数初始化结构体:
(运行有问题)
struct Point{
int x,y;
Point(){};
Point(int _x, int _y):x(_x),y(_y){};
}pt[10];
int main()
{
int num=0;
for(int i=1;i=3;i++){
for (int j=1;j<=3;j++){
pt[num++]=Point(i,j); //直接使用构造函数
}
}
for(int i=0;i<=num;i++){
printf("%d,%d\n",pt[i].x,pt[i].y);
}
return 0;
}
浮点数的比较 考虑到精确度
const double eps=1e-8;
#define Equ(a,b)((fabs((a)-(b)))<(eps));
多点测试
while(scanf("%d",&n)=!EOF){};
或者
while(T--){};
日期处理:
我的想法:
先用除、取余将两个日期的年月日分别提出来。假设第一个日期在前面。否则交换。
然后若为同一年,直接按月日计算出天数;
若不为同一年,将第一个日期加到与第二个日期相差一年(这期间相加的各年份的天数要算上),然后算出第一个日期离后一年有多少天,第二个日期距离该年开始有多少天,然后再相加。
字符串处理,说反话
int main()
{
int num=0;
char ans[90][90];
while(scanf("%s",ans[num])&&(*ans[num]!='a')) //scanf直接按空格可分开
{
num++;
}
for(int i=num-1;i>=0;i--){
printf("%s",ans[i]);
if(i>0)
printf("\n");
}
return 0;
}
//也可以用gets来一次性先得到整个数组,再按空格分开
sort(首个数指针,末尾的数的后一个指针,cmp)
cmp:
bool cmp(int a, int b){
return a>b;
} //由大到小排序
递归
全排列:
#include <cstdio>
const int maxn = 11;
int n, p[maxn], hashTable[maxn]={false};//长度,排列数,标记
void generateP(int index){
if(index==n+1){
for(int i=1;i<=n;i++){
printf("%d",p[i]);
}
printf("\n");
return;
}
for(int x=1;x<=n;x++){
if(hashTable[x]=false){
p[index]=x;
hashTable[x]=true;
generateP(index+1);
hashTable[x]=false; //已处理完p[index]为x的子问题,还原状态(如果少了这一行就只能输 出第一个排列)
}
}
}
int main(){
n=3; //1~3的全排列
generateP(1); //从p[1]的开始填
return 0;
}
n皇后 count不加::会报错:Reference to ‘count’ is ambiguous
const int maxn = 11;
int n, p[maxn], hashTable[maxn]={false};//长度,排列数,标记
int count = 0;
void generateP(int index){
if(index==(n+1)){
bool flag = true;
for (int i=1; i<=n; i++) {
for(int j=i+1;j<=n;j++){
if(abs(i-j)==abs(p[i]-p[j])){
flag=false;
}
}
}
if(flag){
::count++;
for(int i=1;i<n;i++)
printf("%d",p[i]);
printf("\n");
}
return;
}
for(int x=1;x<=n;x++){
if(hashTable[x]==false){
p[index]=x;
hashTable[x]=true;
generateP(index+1);
hashTable[x]=false; //已处理完p[index]为x的子问题,还原状态
}
}
}
int main(){
n=5; //1~3的全排列
generateP(1); //从p[1]的开始填
printf("%d",::count);
return 0;
}
n皇后改进,回溯法(在发现不管怎样都会失败后及时跳出回归)
void generateP(int index){
if(index == n+1){//递归边界
count++; //能到这里的一定合法
return;
}
for(int x=1;x<=n;x++){
if(hashTable[x]==false){
bool flag = true;
for(int pre=1;pre<index;pre++){
if(abs(index-pre)==abs(x-p[pre])){
flag=false; //与之前的皇后在一条对角线,冲突,跳出这个递归
break;
}
}
if(flag){ //如果可以把皇后放在x行
p[index]=x;
hashTable[x]=true;
generateP(index+1);
hashTable[x]=false;
}
}
}
}
区间贪心
1.如果有小区间被另一大区间包含,则去掉大区间(因为留下小区间可以有更多空间容纳其它区间)
2.按左端点从大到小排序,左端点一样的按右断点从小到大排序
问题2:给出N个闭区间,求最少需要确定多少个点,才能使每个闭区间中都至少存在一个点
二分法
第一种问题可能查找失败,会返回-1,第二种问题不会查找失败,因为就算没有也会假设其存在的位置
一.查找序列中“是否存在”满足某条件的元素
// [left,right],传入的初值为[0,n-1]
int binarySearch(int A[],int left,int right, int x){
while(left<=right){ //如果left>right就没办法形成闭区间了
mid = (left+right)/2;
if(A[mid]==x) return mid;
else if(A[mid]>x){
right=mid-1;
}else{
left=mid+1;
}
}
return -1; //查找失败,返回-1
}
int main(){
const int n=10;
int A[n]={1,3,4,6,7,8,10,11,12,15};
printf("%d%d\n",binarySearch(A,0,n-1,6),binarySearch(A,0,n-1,9));
return 0;
}
二. 解决“寻找有序序列第一个满足某条件元素的位置”问题的固定模版(若不存在,也可以返回一个“假设它存在,它应该在的”位置)
//二分区间为左闭右闭的[left,right],初值必须能覆盖解的所有可能取值
int solve(int left, int right){
int mid;
while(left<right){ //对[left,right]来说,left==right意味着找到了唯一位置
if(条件成立){
right = mid;
}else{
left = mid+1;
}
}
return left; //返回夹出来的位置
}
1.需要找第一个小于等于x的元素,条件应为A[mid]<=x;
2.需要找第一个大于x的元素,条件应为A[mid]>x;
因为第一个出现的满足条件的,一定是从左往右第一个出现的满足条件的(木棒切割问题刚好相反);所以若条件成立,此时的A[mid]一定在最后得到的正确答案的右侧(或其本身就是答案),所以满足条件后 right=mid;
如果想要找最后一个满足条件C的元素,可以找到第一个不满足条件C的元素,然后将该位置-1即可
如木棒切割问题,因长度与段数大小约成反比,所以“段数至少为k”是要找到最后一个满足条件的长度。(长度从小到大,则段长从大到小)
n=3, k=7 , 三根长度分别为:10,15,24时
L: 12 6 9 8 7
K: 3 7 4 5 6 (如表,段数与长度约成反比)
int n,k;
int f(int A[],int l){
int result=0;
for(int i=0;i<n;i++){
result += A[i]/l;
}
return result;
}
bool cmp(int a,int b){
return a>b;
}
int binarSolve(int a[],int left,int right){ //left和right是长度, 长度是从小到大
//所以结果也是从满足到不满足,与之前相反
int mid,count;
while(left<right){
mid=(left+right)/2;
count=f(a,mid);
if(count<k) //题目问至少有k段,要找到最后一个符合条件的,所以找到第一个不符合 条件的然后-1
right=mid;
else
left=mid+1;
}
return left-1;
}
int main(){
int result,A[n];
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++){
scanf("%d",&A[i]);
}
sort(A,A+n,cmp); //选出最大的木棒长度,分段长度不能超过它
int l = A[0];
result = binarSolve(A, 1, l);
printf("the result is %d\n",result);
return 0;
}
左开右闭区间(left, right] : 修改两处:
1.left<right 改为left+1<right
2.left=mid+1 改为left=mid
//二分区间为左开右闭的(left,right],初值必须能覆盖解的所有可能取值,且left比最小取值小1
int solve(int left, int right){
int mid;
while(left+1 <right){ //对[left,right]来说,left==right意味着找到了唯一位置
if(条件成立){
right = mid;
}else{
left = mid;
}
}
return left; //返回夹出来的位置
}
三. 求√2的近似值(精确到10^-5)
相当于求f=x*x-2 的零点
const double eps=1e-5;
double f(double x){
return x*x;
}
double calSqrt(){
double left=1,right=2,mid; //(√2)在1与2之间
while((right-left) > eps){
mid = (right+left)/2;
if(f(mid)>2){ //即当mid>√2 时
right=mid;
}else{ //这里的else相当于 f(mid)<2, 因为mid的平方不能能为2,√2不是一个有理数
left=mid;
}
}
return mid;
}
//求函数零点问题
const double eps=1e-5;
double f(double x){
//函数内容
}
double solve(double L, double R){
double right = R, left=L, mid;
while((right-left)>eps){
mid=(left+right)/2;
if (f(mid)>0)
right=mid;
else
left=mid;
}
return mid;
}
快速幂
1.二分幂,快速幂的递归写法
typedef long long LL;
//求a^b%m 的递归写法, 例如a^4%m=a^2*a^2%m, a^5%m=a*a^4%m;
LL binaryPow(LL a,LL b,LL m){
if(b==0)
return 1;
else if(b&1) //b为奇数
return a*binaryPow(a,b-1,m)%m;
else{
LL mul = binaryPow(a,b/2,m);
return mul*mul%m;
}
}
2.快速幂的迭代写法
typedef long long LL;
//求a^b%m 的迭代写法,将b看作2进制数,当某一位为1时才需要乘入结果
LL binaryPow(LL a,LL b,LL m){
int ans=1;
while(b>0){
if(b&1){ //b为奇数是,代表最后一位为1(二进制最后一位)
ans=ans*a%m;
}
a=a*a; //a平方
b>>=1; //将b的二进制右移1位,即b>>=1或者b=b/2
}
return ans;
}
⚠️注:
1.初始时a有可能大于m,则进入函数前就让a对m取模
2.如果m为1,可以直接在函数外特判为0(任何数对1取模都为0)
two pointers
1.求递增数组中和相加为m的两个数(i=j时不行,只能i<j,因为不能把一个数选两次)
while(i<j){
if(a[i]+a[j]==m)
{
printf("%d %d\n",i,j);
i++;
j--;
}
else if(a[i]+a[j]>m)
j--;
else
i++;
}
2.序列合并问题(从小到大排列)
int merge(int A[],int B[],int C[],int n, int m){
while(i<n&&j<m){
int i=0,j=0,index=0;
if(A[i]<A[j]){
C[index++]=A[i++];
}
else{
C[index++]=B[j++];
}
}
while(i<n) C[index++]=A[i++]; //剩下来的多的接到后面
while(j<m) C[index++]=B[j++];
return index; //返回序列C的长度
}
3.归并排序
1>递归实现
const int maxn=100;
//将数组A的[L1,R1]与[L2,R2]区间合并为有序区间(L2即为R1+1)
void merge(int A[],int L1,int R1,int L2,int R2){
int i=L1,j=L2;
int temp[maxn],index=0;
while(i<R1&&j<R2){
if(A[i]<A[j]){
temp[index++]=A[i++];
}
else{
temp[index++]=j++;
}
}
while(i<=R1) temp[index++]=A[i++]; //剩下来的多的接到后面
while(j<=R2) temp[index++]=A[j++];
for(i=0;i<index;i++){
A[L1+i]=temp[i]; //将合并后的序列赋值回数组A
}
}
//将array数组当前区间[left,right]进行归并排序
void mergeSort(int A[],int left,int right){
int mid =(left+right)/2;
mergeSort(A,left,mid); //递归,将左子区间[left,mid]归并排序
mergeSort(A,mid+1,right); //递归,将右子区间[mid+1,right]归并排序
merge(A,left,mid,mid+1,right); //左右子区间合并排序
}
2>非递归实现
void mergeSort(int A[]){
//step为组内元素个数,step/2为左子区间元素个数,⚠️注意等号可以不取
for(int step=2;step/2<=n;step*=2){
for(int i=1;i<=n;i+=step){ //对每一组
int mid = i+step/2-1; //左子区间元素个数为step/2
if(mid+1<=n){ //右子区间存在元素则合并
//左子区间为[i,mid],右子区间为[mid+1,min(i+step-1,n)]
merge(A,i,mid,mid+1,min(i+step-1,n));
}
}
}
}
4.快速排序
//对区间[left,right]进行划分(按中间值划分)
int Partition(int A[],int left,int right){
int temp=A[left]; //讲A[left]作为中间值存在temp中
while(left<right){
while(left<right && A[right]>temp) right--;
A[left]=A[right]; //将此时right所指的值放到A[left]
while(left<right && A[left]<=temp) left++;
A[right]=A[left];
}
A[left]=temp;
return left; //返回相遇下标
}
//快速排序,left与right初值为序列首位下标(例如1与n)
void quickSort(int A[],int left,int right){
if(left<right){ //当前区间的长度不超过1
//将[left,right]按A[left]一分为二
int pos= Partition(A,left,right);
quickSort(A,left,pos-1);
quickSort(A,pos+1,right);
}
}
生成随机数:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand((unsigned)time(NULL));
for(int i=0;i<10;i++){
printf("%d",rand());
}
return 0;
}
//rand()%(b-a+1)+a的范围是[a,b]
//例如:rand()%2 范围是[0,1] rand()%5+3 范围是[3,7]
rand()函数只能是[0,RAND_MAX]范围内整数,RAND_MAX是stdlib.h中的一个常数,在不同系统中该常数的值不同。如:32767,若范围要为[a,b]且左右两端超过RAND_MAX, 则可以
(int)((double)rand()/32767*(b-a+1)+a), 相当于这个浮点数就是[a,b]范围内的比例位置。
随机快排
round()函数是cmath标头的库函数,用于对最接近该数字的给定值进行四舍五入,一半的情况下舍入为零,它接受一个数字并返回四舍五入的值。
int randPartition(int A[],int left,int right){
//生成[left,right]内的随机数
int p = (round(1.0*rand()/RAND_MAX*(right-left)+left));
swap(A[p],A[left]); //交换A[p]与A[left]
int temp = A[left]; //以下与原先算法一样
while(left<right){
while(left<right && A[right]>temp) right--;
A[left]=A[right];
while(left<right && A[left]<=temp) left++;
A[right]=A[left];
}
A[left]=temp;
return left;
}
随机选择算法
//随机选择算法,从A[left,right]中返回第K大的数
int randSelect(int A[],int left,int right,int K){
if(left==right) return A[left];
int P = randPartition(A,left,right);
int M = P-left+1; //A[p]是A中[left,right]中的第M大
if(M==K) return A[P];
if(K<M){
return randSelect(A,left,p-1,K); //往主元左侧找第K大
}
else
return randSelect(A,p+1,right,K-M); //往主元右侧找第K-M大
}
数学问题
最大公约数
辗转相除法(欧几里得算法)(其他写法需要注意a和b的大小关系)
int gcd(int a ,int b){
if(b==0)
return a;
else return gcd(b,a%b); //a小于b则会交换位置
}
或者:
int gcd(int a,int b){
return !b?a:gcd(b,a%b);
}
素数筛法(复杂度O(nloglogn))
从小到大1~n,筛去素数的倍数。如果没有被前面的步骤筛去,那么从小到达轮着的下一个没被筛去的一定是素数。如2为素数,则筛掉4,6,8… ,下一个是3为素数,筛掉6,9,12…
const int maxn=101; //表长
int prime[maxn],pNum=0;
bool p[maxn]={0}; //i为素数时,p[i]=true
void Find_Prime(){
for(int i=2;i<maxn;i++){
if(p[i]==true){
prime[pNum++]=i;
for(int j=i+i;j<maxn;j+=i){ //筛去素数的倍数
p[j]=false;
}
}
}
}
大整数
用数组存数字,定义结构体,数组低位存放大整数的低位
构造函数初始化结构体
struct bign{
int d[1000];
int len;
bign(){
memset(d,0,sizeof(d));
len=0;
}
}
大整数加法:
bign add(bign a,bign b){
bign c;
int carry;
for(int i=0;i<a.len || i<b.len;i++){
int temp=a.d[i]+b.d[i]+carry;
c.d[c.len++]=temp%10; //个位为其结果
carry=temp/10;
}
if(carry!=0){
c.d[c.len++]=carry;
}
return c;
}
当使用STL的queue时,元素入队的push操作只是制造了该元素的一个副本入队,因此在入队后对原元素的修改不会影响队列中的副本,对队列中副本的修改也不会影响到原元素,因此,当需要对队列中元素进行修改而不是仅仅访问时,队列中存放的元素最好不要是元素本身,而是他们的编号(如果是数组的话就是下标)