容易忽略的算法知识点

算法小知识

结构体之间可以相互赋值

n<<1 == n*2
n>>1 == n/2
1<<n == 2^n
a^b == (a+b)%2

a&1==1 说明a是奇数
b&1==0 说明b是偶数

绝对值:fabs()函数可适用于整型和浮点型
次幂:pow() 函数只适用于浮点型
开平方:sqrt() 函数只适用于浮点型
根下平方和:hypot()函数只适用于浮点型(a=b2+c2a=\sqrt {b^2+c^2}

整型除以浮点型 得 浮点型
整形减去浮点型 也得 浮点型

唯一分解定理:一个数能且只能分解为一组质数的乘积

cin.peek 是从看缓冲区中的下一个字符,但不接收

reverse() 函数不仅可用于数组,还可用于 string 和 STL 容器

蓝桥杯不能使用 to_string、stoi、stol、auto、unordered_map、unordered_set 这些函数

可以添加 std::ios::sync_with_stdio(false);cin.tie(0),cout.tie(0); 用于加快 cin 和 cout 的运行速度
还可以添加 #pragma GCC optimize(2)#pragma GCC optimize(3) 用于卡常O2优化

在非递归的函数前加上 inline 可以提高运行速度

如果 g++ 编译器不够快,可以考虑 clang++;

按照NOIP评测机的标准,1 秒完成的复杂度运算:
O(n) 的话就是 108,保险起见 106
O(n2) 的就是 104,保险起见 103

(阶乘)10000! 有35659位
(次幂)2130 有 39 位

(阶乘之和)只有 第一位 和 第三位 不是以 3 结尾,其他都是以 3 结尾(没有以 0 结尾的)
举例:1 3 9 33 153 873 5913 46233 409113 4037913 43954713 522956313 6749977113

求位数的公式

int s = log10(n)+1;  //log10是头文件自带的

2n 的求位数公式

int s = n*log10(2)+1;

n!的末尾 0 的个数

int n,ans=0;
cin>>n; //阶乘
while(n)
{
    ans+=n/5;
    n/=5;
}
cout<<ans; //个数

第 K 小的数(nth_element)

int a[] = {1,5,6,9,8,7,4,2,3};
int len = sizeof(a)/sizeof(a[0]);
nth_element(a,a+k,a+len);
cout<<a[k];

//注意存在第 0 小, 所以第 k 小相当于第 k+1 小

最大数 与 最小数(max_element 与 min_element)

int a[] = {1,5,6,9,8,7,4,2,3};
int len = sizeof(a)/sizeof(a[0]);
auto p = max_element(a,a+len);
cout<< p-a <<' '<< *p;
cout<< endl;
auto q = min_element(a,a+len);
cout<< q-a <<' '<< *q;

output:
3 9
0 1

printf("%-5d", a)

printf("%-5d",2); //代表向左靠齐
printf("%-5d",-1);

output:
2    -1

整型 最大值与最小值

INT_MAX 和 INT_MIN 在头文件< limits.h >中,代表 int 型的最大值与最小值,用法:

#include <limits.h>
cout<<INT_MAX<<' '<<INT_MIN<<endl;

Output:
2147483647 -2147483648

变量可以直接加负数 或 分数

int sum = 0;
sum += -1;

double sum = 0;
sum += 1.0/3;

两个函数:toupper() 和 tolower(c)

toupper(c): 能将字符c转换为大写英文字母
tolower(c): 能将字符c转换为小写英文字母

五个函数:islower() 和 isupper() 和 isalpha() 和 isalnum() 和 isdigit()

islower(c): 当参数c为小写英文字母(a-z)时,返回 非0 值,否则返回 0
isupper(c): 当参数c为大写英文字母(A-Z)时,返回 非0 值,否则返回 0
isalpha(c): 当参数c为英文字母 (A-Z) 或 (a-z) 时,返回 非0 值,否则返回 0
isalnum(c):当字符变量c为字母或数字,返回 0,否则返回 非0值

isdigit(c):当字符变量c为数字,返回 0,否则返回 非0值

string 字符串之间可以比较大小

"1814/09/06 day" 小于 "2014/09/06 day"

四舍五入的方法

double sum = 3.7;
sum = (int)(sum + 0.5);
int a = 1, b = 2, sum;
sum = (int)((a + b) * 1.0 / 2.0)
//另一种程序自动四舍五入的方法
double a=0.004;
double b=0.005;
printf("%.2lf\n",a);
printf("%.2lf",b);

//从结果来看,对于格式化,程序会自动四舍五入
output:0.00
	   0.01

cin 的妙用

如果你定义了一个 int 型,那么当 cin 输入表达式的值是就会检测输入的是不是数字,如果程序发现用户输入了错误内容时,会返回 0,否则返回 1

int a;
if(cin>>a) cout<<"输入格式符合"<<endl;
cin.clear();
if(!(cin>>a)) cout<<"输入格式不符";

output:
1
输入格式符合
s
输入格式不符

scanf 的妙用

在你输入字母时,因为scanf("%d",&num)中格式要求是整型(%d),所以不符合,返回值为 0,而当你输入任何数字时,scanf的返回值都是 1

int num,tmp;
int a = scanf("%d",&num);
int b = scanf("%d %d",&num, &tmp);
int c = scanf("%d",&num);
cout << a <<' '<< b <<' '<< c;

input:
1
1 2
a

output:
1 2 0

接收一行字符串

string s;
getchar(); //正常情况要加上
for (int i = 0; i < n; i++)
	getline(cin,s); //接收多行数据

// 空格在 getline中也算一个字符,上面适用于 string

char str[20];
while(gets(str)!=NULL)
	printf("%s",str);

// 空格在 gets中也算一个字符,上面适用于 char[]

典型的字符串的字符输入

while((c=getchar())!='\n') 
{
	...
}

输出固定位小数点的数(可以混用)

// C++
cout<<fixed<<setprecision(7)<<a<<endl

// C
printf("%.7lf",a)

输出固定位数字的数

//C
printf("%05d",a); // a = 3

output: 00003

不同类型的位数

在这里插入图片描述

填充数组(memset 和 fill )

int m=5,n=5,value=1;
int a[n], G[m][n];
vector<int> v(5); //5个元素,自动初始化为 0

//对于一维数组
memset(a,n,value);
fill(a,a+n,value);

//对于二维数组
fill(G[0],G[0]+m*n,value);

//对于容器
fill(v.begin(),v.end(),value);

申请栈空间

int *data = new int[100005];

delete[] data;

求已排列数组两两之间差的最大公约数(一步到位)

//两两相减取最大公约数 
for(int j = 0;j<(n-2);j++)
	temp = max(temp,gcd(a[j+1]-a[j],a[j+2]-a[j+1]));

关于运算符

运算符 描述
& 按位与运算符: 参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0
| 按位或运算符: 只要对应的二个二进位有一个为1时,结果位就为1。
^ 按位异或运算符: 当两对应的二进位相异时,结果为1
~ 按位取反运算符: 对数据的每个二进制位取反,即把1变为0,把0变为1 ;~x 类似于 -x-1
<< 左移动运算符: 运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0。
>> 右移动运算符: 把">>"左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数

下面以变量 a 为 60,b 为 13 举例,二进制格式如下:

a = 0011 1100

b = 0000 1101

-----------------

a & b = 0000 1100

a | b = 0011 1101

a ^ b = 0011 0001

~a  = 1100 0011

a << 2 = 1111 0000

a >> 2 = 0000 1111

打印二进制

将二进制(8个字节)打印成 '*'' ' 的形式
for(int i = 7;i>= 0;i--)    //从字节最左边开始打印
{
    if(x&(1<<i)) putchar('*');
    else putchar(' ');
}
此做法不仅包含了十进制转换二进制,还包含了打印,因为使用 & 时十进制会转换成二进制

那负数怎么办?用补码(即+256再转换成二进制)
if(x< 0) x+= 256;//取补码

结构体 与 STL 的联系

struct Node
{
	string s;
	int n;
	char c;
	Node(string ss,int nn,char cc):s(ss),n(nn),c(cc){}; //构造器 (函数)
};

queue<Node> q;
q.push(Node("123",456,'7')); //直接加入构造器

Node tmp = q.front();
string tmp_1 = tmp.s;
int tmp_2 = tmp.n;
char tmp_3 = tmp.c;

cout<<tmp_1<<tmp_2<<tmp_3;

output: 1234567

lower_bound/upper_bound

begin和end是 int 值,num是有6个元素的 int 型数组名

lower_bound( begin,end,num)查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end
upper_bound( begin,end,num)查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end

int pos1 = lower_bound(num,num+6,7)-num;    //返回数组中第一个大于或等于 7 的下标或 6(没找到)
int pos2 = upper_bound(num,num+6,7)-num;    //返回数组中第一个大于 7 的下标或 6(没找到)

int pos3 = lower_bound(num,num+6,7,greater<int>())-num;  //返回数组中第一个小于或等于 7 的下标或 6(没找到)
int pos4 = upper_bound(num,num+6,7,greater<int>())-num;  //返回数组中第一个小于 7 的下标或 6(没找到)

记得一定要写在最后减去num,不然程序运行不了
补充:若是 STL 则最后可以减去 temp.begin()

补个例子

int a[10] = {0,1,2,3,4,5};
int b1 = upper_bound(a+1,a+1+5,2)-(a+1); //找得到
int b2 = upper_bound(a+1,a+1+5,6)-(a+1); //找不到
cout << b1 <<' '<< b2;

output: 2 5

等差素数列的最小公差(定理)

//如:7,37,67,97,127,157 
//这样完全由素数组成的等差数列,叫等差素数数列,上边的数列公差为 30,长度为 6
//定理:等差素数列 的 最小公差 即为数列长度以内的素数积

30 = 2 * 3 * 5
210 = 2 * 3 * 5 * 7
……以此类推

回文数的判断

bool pd_h(int x)
{

    int y=x,num=0;
    while (y!=0)
    {
        num=num*10+y%10;
        y/=10;
    } 
    if (num==x) return 1;
    else return 0;
}

排列组合

A(4,2)=4!/2!=4*3=12

C(4,2)=4!/(2! * 2!)=4 * 3/(2 * 1)=6

【补充】(A(n,0)=0 ,C(n,0)=0 )

伪随机函数: rand()函数

如果想要表示一个数是从0开始到最大值的,比如说,想要产生一个0-99之间的随机数,那么用法如下

int num = rand() % 100

如果想要产生一个数是从1开始到最大值的,比如说,想要产生一个1-100之间的随机数,那么用法如下

int num = rand() % 100 + 1;

需要注意最后 +1和不 +1的区别,+1的最小值是1,不+1的最小值是0

全排列函数 next_permutation 用法

//建立一个数组
int num[3]={1,2,3};

do{  
  cout<<num[0]<<" "<<num[1]<<" "<<num[2]<<endl;  
}while(next_permutation(num,num+3)); //全排列整个数组

output:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

do{  
  cout<<num[0]<<" "<<num[1]<<" "<<num[2]<<endl;  
}while(next_permutation(num,num+2)); //将 3 改为 2,即只全排列前两个元素

output:
1 2 3
2 1 3

//因为全排列是按照升序排列的,所以若更改数组顺序
int num[3]={2,1,3};

do{  
  cout<<num[0]<<" "<<num[1]<<" "<<num[2]<<endl;  
}while(next_permutation(num,num+3)); //依然全排列整个数组

output:
2 3 1
3 1 2
3 2 1

列表排序的经典问题(不用结构体)

1025 反转链表

数组图案的旋转

N 为正方形数组的行与列(从1开始计数)
原数组A[N+1][N+1],旋转后的数组B[N+1][N+1],存在的关系如下:

顺时针旋转90°: B[j][N-i+1]=A[i][j];
逆时针旋转90°: B[N-j+1][i]=A[i][j];
水平方向翻转 : B[i][N−j+1]=A[i][j];
垂直方向翻转 : B[N-i+1][j]=A[i][j];

判断合法日期

int isDate(int year, int month, int day)
{
	if(year>2020 || year<1900) return 0;  //具体年份依题目而定
	if(month>12 || month<1) return 0;
	if(month==1||month==3||month==5||month==7||month==8||month==10||month==12){
		return (day>=1&&day<=31);
	}
	if(month==4||month==6||month==9||month==11) {
		return (day>=1&&day<=30);
	}
	if((year%4==0&&year%100!=0)||(year%400==0)){
		return (day>=1&&day<=29);
	}
	return day>=1&&day<=28;
}

求 n!的非零尾数

// 第一种方法(知道 n!有多少尾数零时,简化了运算)
int n;cin>>n; //阶乘 n 
int t=n,ans=0;
while(t)
{
    ans+=t/5;
    t/=5;
}
int cnt2=ans,cnt5=ans,ans1=1;
for(int i=1;i<=n;i++) //求阶乘 
{
    int t=i;
    while(cnt2&&t%2==0)t>>=1,cnt2--; // 减少因子 2 的个数 
    while(cnt5&&t%5==0)t/=5,cnt5--; // 减少因子 5 的个数 
    ans1=ans1*t%10; //结果取模 
}
cout<<ans1;


// 第二种方法
int n;cin>>n; //阶乘 n 
int ans=1;
for(int i=1;i<=n;i++)
{
    ans=ans*i%1000000; //完成阶乘运算且控制了数值大小
    while(ans%10==0)
    {
        ans=ans/10;
    }
}
cout<<ans;

超长整型 __int128(可容纳 39 位十进制数字)与 快读、快输

最长的 unsigned long long 只能容纳 19 位十进制数字,所以衍生了自创的超长整型
超长整型 __int128 也只能由 快输 来输出

#include <bits/stdc++.h>
using namespace std;
inline __int128 read() //两条下划线  //39位  //unsigned long long 19--20位 //快读
{
    __int128 x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

inline void write(__int128 x) //快输
{
    if(x<0){putchar('-');x=-x;}
    if(x>9){write(x/10);}
    putchar(x%10+'0');
}

int main()
{
    __int128 a = read();   //新类型
    __int128 b = read();
    int c = 4;   //传统类型
    write(a);write(b);write(c);putchar('\n');  //直接打印
    write(a + b);write(a - b);write(a * b);write(a / b);putchar('\n');  //新类型之间 加减乘除
    write(a + c);write(a - c);write(a * c);write(a / c);putchar('\n');  //新类型 与 传统类型 加减乘除
    return 0;
}

input:
8 4
output:
8 4 4        //实际打印后中间没有空格,为了方便观看而手动加的
12 4 32 2
12 4 32 2

补充:正常版(int)的 快读 快输

inline int read() {
  int x = 0, neg = 1; char op = getchar();
  while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
  while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
  return neg * x;
}

inline void print(int x) {
  if (x < 0) { putchar('-'); x = -x; }
  if (x >= 10) print(x / 10);
  putchar(x % 10 + '0');
}

手写队列(queue)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 100;
struct queue
{
    int l = 0,r = 0,a[maxn];
    void push(int x){
        a[r++] = x;
    }
    int front(){
        return a[l];
    }
    void pop(){
        l++;
    }
    int empty(){
        return l >= r ? 1 : 0;
    }
}q;
int main()
{
	q.push(123);
	cout<<q.front();
	q.pop();
	if(q.empty()) cout<<' '<<456;
}

output: 123456

在这里插入图片描述

函数重载 与 构造函数

//简单的初始化和重载的初始化
struct node{                   		    //定义一个点的座标结构体
    int x,y;                            
    node():x(0),y(0){}                  //默认的构造函数,初始化为x=0,y=0 
    node(int _x,int _y):x(_x),y(_y){}   //重载的构造函数,使用两个参数初始化x,y 
}; 

//使用示例
node a;                                 //默认构造函数,自动初始为(0,0)
node b=node(3,4);                       //使用重载的构造函数,初始化为(3,4)

//复杂类型的初始化,在默认构造函数中实现初始化
struct st{                     		    //学生结构体
    string id;                          //学生id
    int score[10];                      //记录10门课程成绩的数组,-1表示缺考
    bool pass;                          //是否合格的标记
    st(){
        fill(score,score+10,-1);        //初始化为-1,表示缺考
		pass=true;                      //默认合格,不合格时修改标记
    }
};                                      //默认构造函数,定义时便完成初始化

//结构体比较,对 < 的重载,与 sort 联合使用,不用加 cmp 直接排序的作用
struct node{
    int x,y;
    bool operator < (const node &b)const//重载小于号,用于构建优先级关系
    {
        if(x!=b.x)    return x<b.x;
        else          return y<b.y;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章