算法小知识
结构体之间可以相互赋值
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()函数只适用于浮点型()
整型除以浮点型 得 浮点型
整形减去浮点型 也得 浮点型
唯一分解定理:一个数能且只能分解为一组质数的乘积
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;
}
};