[record] 算法基础

306 阅读20分钟

【基础something basic】

1、常见评测结果

  • AC(Accepted)
  • CE(Compile Error)
  • WA(Wrong Answer)
  • TLE(Time Limit Exceeded) 原因:时间复杂度过大;死循环。
  • RE(Runtime Error) 原因:段错误(数组越界、指针乱指导致的非法访问内存);浮点错误(除数为0、模数为0);递归爆栈(递归层数过深)。
  • MLE(Memory Limit Exceeded) 原因:数组过大。
  • PE(Presentation Error) 原因:多输了空格或者换行。
  • OLE(Output Limit Exceeded) 原因:输出了大量的调试信息或特殊数据。

一般的OJ系统每秒能承受的运算次数大概是10^7 ~ 10^8次

2、

因为C++向下兼容,因此一般都是在C++中写C语言的语法。 将代码文件保存为.cpp文件。

3、头文件

推荐使用C++标准的头文件格式。

#include < cstdio >等价于#include < stdio.h > stdio=standard input output

#incluce < cmath >等价于#include < math.h >

⚠️以下全是对double型变量!!!

  • fabs(double x):求绝对值。
  • floor(double x):向下取整。
  • ceil(double x):向上取整。
  • pow(double r, double p):求r^p。
  • sqrt(double x):求x的算术平方根(开根号)。
  • log(double x):求以e为底的对数。 C语言中没有对任意底数求对数的函数,则要使用换底公式。
  • round(double x):四舍五入。

#include < cstring >等价于#include < string.h >

  • strlen(str):返回字符数组中第一个\0前的字符个数。
  • strcmp(str1, str2):返回两个字符串大小的比较结果。按照字典序进行比较,如果str1大于str2则返回一个正数;如果str1=str2则返回0;如果str1<str2则返回一个负数。
  • strcpy(str1, str2):把字符串2赋值给字符串1。
  • strcat(str1,str2):把字符串2接到字符串1后面。
  • sscanf(str, "%d", &n):➡️把str中的内容以%d的格式写入n。 sscanf(str, "%d:%lf,%s", &n, &db, str2);
  • sprintf(str, "%d", n):⬅️把n以%d的格式写入str。

#include < algorithm > using namespace std;

  • max(x, y)/min(x, y) 返回x、y中的最大值/最小值。 可以是浮点数。
  • abs(x) 返回整数x的绝对值。
  • swap(x, y) 交换x、y的值。
  • reverse(it1, it2) 将数组指针或容器的迭代器在[it1, it2)内的元素进行反转。 reverse(a, a+5); reverse(str.begin(), str.begin()+5);
  • next_permutation() 给出一个序列在全排列中的下一个序列。
  • fill() 数组初始化。 fill(a, a+5, 233);
  • sort(首元素地址(必填), 尾元素的下一个地址(必填),比较函数cmp(非必填)) 如果未填写比较函数cmp,sort函数默认按照从小到大的顺序对元素进行排序。 比较函数cmp:a<b从小到大;a>b从大到小。
  • lower_bound(first, last, val) 查找在数组或容器的[first, last)内第一个值不小于(大于等于)val的元素的位置。
  • upper_bound(first, last, val) 查找在数组或容器的[first, last)内第一个值大于val的元素的位置。

#include < string > using namespace std;

  • 注意!!!和< string.h >是不一样的头文件。
  • string str;

string内元素的访问

  • 通过下标访问 for(int i = 0; i < str.length(); i++){ printf("%c", str[i]); } 读入或输出整个字符串
    • #include < iostream > #include < string > using namespace std; cin>>str; cout<<str;
    • #include < cstdio > #include < string > using namespace std; printf("%s", str.c_str());//c_str()将string类型转换为字符数组
  • 通过迭代器访问 string::iterator it; for(string::iterator it = str.begin(); it != str.end(); it++){ printf("%c", *it); }

string常用函数

  • operator+= 将两个string直接拼接起来。 str1 += str2;//将str2直接拼接到str1上
  • compare operator 根据字典序进行比较(==、!=、<、<=、>、>=)。 if(str1 < str2)... if(str1 != str2)... if(str1 >= str2)...
  • substr(pos, len) 返回从pos开始、长度为len的子串,时间复杂度为O(len)。
  • string::npos 一个常数,本身值为-1。但由于是unsigned_int类型,因此可以认为是unsigned_int类型的最大值(4294967295)。
  • find() 时间复杂度为O(nm),n为str的长度,m为str2的长度。
    • str.find(str2),当str2是str的字串时,返回其在str中第一次出现的位置,否则返回string::npos。
    • str.find(str2, pos),从str的pos位开始匹配str2。
  • replace() 时间复杂度为O(str.length())。
    • str.replace(pos, len, str2),把str从pos位置开始、长度为len的子串替换成str2。
    • str.replace(it1, it2, str2),把str的迭代器[it1, it2)范围的子串替换成str2。
  • length()/size() 返回string的长度,时间复杂度为O(1)。
  • insert() 时间复杂度为O(n)。
    • insert(pos, string),在pos处插入string。
    • insert(it, it2, it3),it为原字符串欲插入位置,it2和it3为待插入字符串的首尾迭代器。串[it2, it3)将被插在it处。
  • erase()
    • 删除单个元素,时间复杂度为O(n)。 str.erase(it)
    • 删除[first, last)区间内元素,时间复杂度为O(n)。 str.erase(起始迭代器,末尾迭代器的下一个地址)
    • 删除指定长度元素。 str.erase(pos, length)
  • clear() 清空string中的数据。

#include < vector > using namespace std;

  • vector< typename > name;
  • typename如果也是一个stl容器,定义的时候要在>和>之间加上空格。例如:vector< vector< int > > name。

vector二维数组

  • 二维都可变长的数组vector< vector< int > > name;
  • 一维定长,一维可变长的数组vector< int > vi[100];

vector内元素的访问

  • 通过下标访问 vi[0]
  • 通过迭代器访问 注意!!!end()取的是尾元素的下一个地址。 在常见的stl容器中,只有vector和string支持vi.begin()+3这种写法。 vector< int >::iterator it = vi.begin(); for(int i = 0; i < 5; i++){ prinf("%d",*(it+i)); } vector容器不支持it<vi.end()写法。 for(vector< int >::iterator it = vi.begin(); it != vi.end(); it++){ printf("%d", *it); }

vector常用函数

  • push_back(x) 在vector后面添加一个元素x,时间复杂度为O(1)。
  • pop_back() 删除vector的尾元素,时间复杂度为O(1)。
  • insert(it, x) 向vector的任意迭代器it处插入一个元素x,时间复杂度为O(n)。
  • erase()
    • 删除单个元素,时间复杂度为O(n)。 vector.erase(it)
    • 删除[first, last)区间内元素,时间复杂度为O(n)。 vector.erase(起始迭代器,末尾迭代器的下一个地址)
  • size() 返回vector中元素的个数,时间复杂度为O(1)。
  • clear() 清空vector中的所有元素,时间复杂度为O(n)。

#include < set > using namespace std;

  • set< typename > name;
  • set是一个内部自动有序(递增)且不含重复元素的容器。

set内元素的访问

  • 只能通过迭代器访问 set< int >::iterator it; for(set< int >::iterator it = st.begin(); it != st.end(); it++){ printf("%d", *it); }

set常用函数

  • insert(x) 将x插入set容器中,并自动递增排序和去重,时间复杂度为O(logn)。
  • find(value) 返回set中对应值为value的迭代器,时间复杂度为O(logn)。
  • erase()
    • 删除单个元素。 通过迭代器st.erase(it),时间复杂度为O(1)。 通过值st.erase(value),时间复杂度为O(logn)。
    • 删除[first, last)区间内元素,时间复杂度为O(last-first)。 st.erase(起始迭代器,末尾迭代器的下一个地址)
  • size() 返回set内元素的个数,时间复杂度为O(1)。
  • clear() 清空set中的所有元素,时间复杂度为O(n)。

#include < map > using namespace std;

  • map<typename1, typename2> mp;

map内部是用红黑树实现的,map会以键从小到大自动排序。

map内元素的访问

  • 通过下标访问 mp['c'] = 20;
  • 通过迭代器访问 map<char, int> mp; mp['m'] = 20; mp['a'] = 30; mp['p'] = 40; for(map<char, int>::iterator it = mp.begin(); it != mp.end(); it++){ //用it->first来访问键,it->second来访问map printf("%c %d\n", it->first, it->second); }

map常用函数

  • find(key) 返回键为key的映射的迭代器,时间复杂度为O(log n)。 map<char, int>::iterator it = mp.find('m'); printf("%c %d\n", it->first, it->second);
  • erase()
    • 删除单个元素,时间复杂度为O(1)。 mp.erase(key) mp.erase('a'); mp.erase(it) map<char, int>::iterator it = mp.find('a'); mp.erase(it);
    • 删除[first, last)区间内元素,时间复杂度为O(last-first)。 mp.erase(起始迭代器,末尾迭代器的下一个地址) mp.erase(it, mp.end());
  • size() 返回map的键值对数,时间复杂度为O(1)。 mp.size();
  • clear() 清空map中的全部元素,时间复杂度为O(n)。 mp.clear()

#include < queue > using namespace std;

  • queue< typename > name; 先进先出

queue常用函数

  • push(x) 将x入队,时间复杂度为O(1)。

使用front()和pop()之前,必须用empty()判断队列是否为空

  • front()/back() 获得队首元素/队尾元素,时间复杂度为O(1)。
  • pop() 令队首元素出队,时间复杂度为O(1)。
  • empty() 检测queue是否为空,返回true为空,返回false为非空,时间复杂度为O(1)。
  • size() 返回queue内元素的个数,时间复杂度为O(1)。

  • priority_queue< typename > name; 优先队列,用堆实现,在优先队列中,队首元素一定是当前队列中优先级最高的一个。

priority_queue常用函数

  • push(x) 将x入队,时间复杂度为O(logn)。

使用top()之前,必须用empty()判断队列是否为空

  • top() 获得队首元素(堆顶元素),时间复杂度为O(1)。
  • pop() 令队首元素出队,时间复杂度为O(logn)。
  • empty() 检测queue是否为空,返回true为空,返回false为非空,时间复杂度为O(1)。
  • size() 返回queue内元素的个数,时间复杂度为O(1)。

优先级的设置

  • 基本数据类型的优先级设置 priority_queue< int, vector< int >, less< int > > q; less(从大到小,大根堆)数字越大优先级越大 greater(从小到大,小根堆)数字越小优先级越大
  • 结构体的优先级设置
    • struct fruit{ string name; int price; friend bool operator < (fruit f1, fruit f2){ return f1.price < f2.price;//价格高的优先级高,和sort相反 } }
    • struct cmp{ bool operator () (fruit f1, fruit f2){ return f1.price < f2.price; } } priority_queue< fruit, vector< fruit >, cmp > q; 如果结构体内的数据较为庞大(比如出现了字符串或数组),建议使用引用型来提高效率。比如:friend bool operator < (const fruit &f1, const fruit &f2)和bool operator () (const fruit &f1, const fruit &f2)。

#include < stack > using namespace std;

  • stack< typename > name; 后进先出

stack常用函数

  • push(x) 将x入栈,时间复杂度为O(1)。

使用top()和pop()之前,必须用empty()判断栈是否为空

  • top() 获得栈顶元素,时间复杂度为O(1)。
  • pop() 弹出栈顶元素,时间复杂度为O(1)。
  • empty() 检测stack是否为空,返回true为空,返回false为非空,时间复杂度为O(1)。
  • size() 返回stack内元素的个数,时间复杂度为O(1)。

#include < utility > using namespace std;

  • pair< typename1, typename2 > name; pair<string, int> p; pair<string, int> p("haha", 5); 临时构建 pair<string, int>("haha", 5); make_pair("haha", 5);

pair内元素的访问

  • p.first = "haha"; p.second = 5;

pair常用函数

  • compare operator 先以first的大小进行比较,若相等再以second的大小进行比较(==、!=、<、<=、>、>=)。

pair常见用途

  • 作为map的键值对插入 mp.insert(pair<string, int>("haha", 5)); mp.insert(make_pair("haha", 5));

4、基本数据类型

  • int 10^9以内或者32位的整数。
  • long long 10^18以内或者64位的整数。 long long型被赋大于2^31-1(int型的最大整数范围)的初值,需要在初值后面加上LL。(long long num = 123456789012345LL;)
  • float 不要碰float会变得不幸。
  • double 碰到浮点型的数据都应该使用double来存储。
  • char 用单引号。 大写字母在前,小写字母在后(小写字母比大写字母的ASCII码大32)。
  • 字符串常量 C语言中没有一种单独的基本数据类型可以存储字符串常量,只能用字符数组;C++有string类型。 赋值:char str1[25]="shabi"; char str2[25]="niemade" 输出:printf("%s, %s", str1, str2);
  • bool 在C语言中必须添加stdbool.h头文件才能使用,在C++中可以直接使用。

5、定义常量

const 数据类型 变量名 = 常量; #define 变量名 常量

  • define常用于宏定义。
  • 不加分号!!!!!!(头文件也不加分号,maybe带#都不加分号

6、输入输出

尽量使用scanf和printf。

  • 原因:虽然cin和cout可以不指定输入输出格式比较方便,但是cin和cout消耗的时间比scanf和printf多。
  • scanf的**%c格式**可以读入空格和换行。(臭小子你怎么不变粗
  • scanf的其他除了%c以外的格式不可以读入空格和换行,并以空格和换行为结束判断标志。
  • %md、%0md、%.md右对齐

getchar()和putchar()用来输入和输出单个字符。

  • char c = getchar(); putchar( c );
  • getchar可以读入空格和换行。

gets()和puts()用来输入和输出一行字符串。

  • gets()可以读入空格,以换行为结束判断标志。
  • scanf完一个整数后,如果要使用gets(),要先用getchar()接收换行,再用gets()。
  • puts()输出一行字符串后紧跟一个换行。

⚠️

  • 新建字符数组时,字符数组的长度要比实际存储字符串的长度至少多1,用来存储\0。
  • 如果不是用scanf的%s或者gets来输入字符串(比如用getchar),则需要手动在每个输入的字符串后加入\0,否则printf和puts会因为无法识别字符串末尾而输出一大堆乱码。

7、break和continue

break:跳出当前循环,执行循环体后的语句。 continue:跳过continue后面循环体内的语句,进入下一轮循环。

8、数组

如果数组大小较大(10^6级别),需要将其定义在主函数外面,否则程序会异常退出。

  • 原因:函数内部申请的局部变量来自系统栈,允许申请的空间较小;而函数外部申请的全局变量来自静态存储区,允许申请的空间较大。

数组初始化。

  • menset:初值为0或-1,速度较快 #include <cstdio> #include <cstring> menset(数组名, 值, sizeof(数组名));
  • fill:初值为任意值 #include <cstdio> #include <algorithm> using namespace std; fill(a, a+5, 233);

数组作为函数参数时,一维数组不需要写长度,二维数组的第二维需要写长度。 void change(int a[], int b[][5]) 数组作为参数时,在函数中对数组元素的修改就是对原数字元素的修改。

9、指针

指针是unsigned类型的整数。

10、结构体

结构体普通变量s.id; 结构体指针变量(*p).id;或p->id;

11、浮点数的比较

进行浮点数的比较时,由于浮点数在计算机中的存储并不总是精确的,而C/C++中的“==”操作是完全相同才能判定为true,因此要引入一个极小数eps=1e-8来修正误差。

const double eps=1e-8;

  • == #define Equ(a, b) ((fabs((a)-(b)))<(eps))
  • > a比b的最大范围大 #define More(a, b) (((a)-(b))>(eps))
  • < a比b的最小范围小 #define Less(a, b) (((a)-(b))<(-eps))
  • >= a比b的最小范围大 #define NotLessThan(a, b) (((a)-(b))>(-eps))
  • <= a比b的最大范围小 #define NotMoreThan(a, b) (((a)-(b))<(eps))

圆周率 const double pi = acos(-1.0);

  • 注意!!!在C/C++中,arccos都写成a,同理。

【SORTING】

thought

1、排名实现

在实现完排序的基础上,有两种方法实现排名。

(1)定义一个结构体

//让数组第一个元素的排名为1
stu[0].rank = 1;
//从数组第二个元素开始遍历
for(int i = 1; i < n; i++){
	//当前元素等于前一个元素的成绩,则当前元素的排名等于上一个元素的排名
	if(stu[i].score == stu[i - 1].score)
		stu[i].rank = stu[i - 1].rank;
	//否则当前元素的排名等于数组下标+1
	else stu[i].rank = i + 1;
}

(2)直接输出

int rank  = 1;
//从数组第一个元素开始遍历
for(int i = 0; i < n; i++){
	//如果元素不是数组第一个元素且当前元素的成绩不等于前一个元素的成绩,排名等于数组下标+1
	if(i > 0 && stu[i].score != stu[i-1].score)
		rank = i + 1;
	//输出信息
	...
}

sort algorithm

在这里插入图片描述

1、冒泡排序

对n个数进行排序,总共需n-1趟。 从左到右依次两两比较、交换,大的在右边。(第i趟中,序列里右边i-1个元素的位置固定不变,因此每趟比较中比较的指针只移动到第[n-(i-1)]-1即n-i个位置)

void bubbleSort(){
    for(int i = 1; i <= n; i++){
        for(int j = 0; j <= n - i; j++){
            if(a[j] > a[j+1]){
                int temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
}

2、简单选择排序

对n个数进行排序,总共需n趟。 每趟里序列分成已排序序列和未排序序列,指针i指向未排序序列的第一个元素,从第i个元素和第n个元素之间的序列,选择一个最小的,下标为k,和第i个元素进行交换,指针i向后移动。

void selectSort(){
	for(int i = 1;i <= n; i++){
		int k = i;
		for(int j = k; j <= n ; j++){
			if(A[j] < A[k]) k = j;
		}
		
		int temp = A[i];
		A[i] = A[k];
		A[k] = temp;
	}
}

3、直接插入排序

对n个数进行排序,总共需n-1趟。 第i趟中,指针i、j指向待插入有序序列的元素,从i往前枚举,temp存放i指向的元素,如果j指向的元素小于j前面的元素,把j前面的元素后移,指针j向前移动;最后指针j所指的位置就是temp该插入的位置。

void insertSort(){
	for(int i = 2; i <= n; i++){
		int temp = A[i], j = i;
		while(j > 1 && temp < A[j-1]){
			A[j] = A[j-1];
			j--;
		}
		A[j] = temp;
	}
}

4、归并排序

5、快速排序

【HASH】

thought

1、空间换时间

数字型,且数量级小于10^5。 直接把输入的数作为数组下标来对数进行统计。

2、字符串hash

字符串hash是指将一个字符串S映射为一个整数,使得该整数可以尽可能唯一地代表字符串S。之后再利用数组处理。

//只有大写字母
int hashFunc(char S[], int len){
	int id = 0;
	for(int i = 0; i < len; i++){
		id = id * 26 + (S[i] - 'A');
	}
	return id;
}

//大小写字母
int hashFunc(char S[], int len){
	int id = 0;
	for(int i = 0 ; i < len; i++){
		if(S[i] >= 'A' && S[i] <= 'Z'){
			id = id * 52 + (S[i] - 'A');
		}else if(S[i] >= 'a' && S[i] <= 'z'){
			id = id * 52 + (S[i] - 'a') + 26;
		}
	}
	return id;
}

//包含数字
int hashFunc(char S[], int len){
	int id = 0;
	if(S[i] >= 'A' && S[i] <= 'Z'){
		id = id * 62 + (S[i] - 'A');
	}else if(S[i] >= 'a' && S[i] <= 'z'){
		id = id * 62 + (S[i] - 'a') + 26;
	}else if(S[i] >= '0' && S[i] <= '9'){
		id = id * 62 + (S[i] - '0') + 26 + 10;
	}
	return id;
}

//固定末尾为数字
int hashFunc(char S[], int len){
	int id = 0;
	for(int i = 0; i < len -1; i++){
		id = id * 26 + (S[i] - 'A');
	}
	id = id * 10 + (S[len - 1] - '0');
	return id;
}

【GREEDY】

thought

1、贪心法

贪心法是求解一类最优化问题的方法,它总是考虑在当前状态下局部最优(或较优)的策略,来使全局的结果达到最优(或较优)。

【BINARY SEARCH】

thought

1、二分查找

mid = ⌊(left + right) / 2⌋

  • C/C++中的"/"是向下取整的。
  • 如果二分上界超过int型数据范围的一半,那么当欲查询元素在序列较靠后的位置时,语句mid = (left + right) / 2中的left + right就有可能超过int而导致溢出,此时一般用mid = left + (right - left) / 2来代替。

2、快速幂(二分幂)

求a^b%m (a < m)

  • 如果初始化a>=m,在进入函数前需要让a对m取模
  • 如果m为1,可以直接在函数外特判结果为0。

快速幂的递归写法 时间复杂度O(logb)

  • 如果b是奇数,那么a ^ b = a * a^(b-1)
  • 如果b是偶数,那么a ^ b = a^(b/2) * a^(b/2)
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
	if(b == 0) return 1;
	if(b % 2 == 1) return a * binaryPow(a, b-1, m) % m;
	else{
		LL mul = binaryPow(a, b/2, m);
		return mul * mul % m;
	}
}

快速幂的迭代写法

  • 初始令ans=1,用来存放累积的结果
  • 判断b的二进制末尾是否为1(表示如果当前位为1,则累积a^2i;判断b&1==1或者b%2==1),如果是的话,令ans*a
  • 令a平方,将b右移一位(除以2)
  • 如果b大于0,返回2
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
	LL ans = 1;
	while(b > 0){
		if(b & 1)
			ans = ans * a % m;
		a = a * a % m;
		b >>= 1;
	}
	return ans;
}

【MATH】

thought

1、最大公约数

一般用gcd(a, b)来表示a和b的最大公约数。

gcd(a, b) = gcd(b, a%b)。 0和任意一个整数a的最大公约数都是a!(作为递归边界)。

int gcd(int a, int b){
	if(b == 0) return a;
	else return gcd(b, a%b);
}
//化简后
int gcd(int a, int b){
	return !b? a : gcd(b, a % b);
}

2、最小公倍数

一般用lcm(a, b)来表示a和b的最小公倍数。

lcm(a,b) = a / gcd(a, b) * b

  • lcm(a, b) = a * b / gcd(a, b),因为a*b在实际运算中可能会溢出,且gcd(a, b)是a和b的最大公约数,所以a / gcd(a, b)一定能整除。因此应写成上式。

3、分数

分数的表示

  • down为非负数。如果分数为负,则分子为负。
  • 如果分数恰为0,则其分子为0,分母为1。
  • 分子和分母没有除1以外的公约数。
struct Fraction{
	int up, down;//分子,分母
}

分数的化简 第一步:如果down为负数,则令down和up都为相反数。 第二步:如果up为0,则令down为1。 第三步:约分。求出up绝对值和down绝对值的最大公约数d,令up和down同时除以d。

Fraction reduction(Fraction result){
	if(result.down < 0){
		result.down = - result.down;
		result.up = - result.up;
	}
	if(result.up == 0){
		result.down = 1;
	}else{
		int d = gcd(abs(result.up), abs(result.down));
		result.up /= d;
		result.down /= d;
	}
	return result;
}

分数的四则运算 按照算式通分运算编写程序。

分数的输出 第一步:化简。 第二步:

  • 如果down为1,说明该分数是整数。
  • 如果up的绝对值大于down,说明该分数是假分数,整数部分为up/down,分子部分为abs(up)%down,分母为down。
  • 如果都不是以上,就为真分数。
void showResult(Fraction r){
	r = reduction(r);
	if(r.down == 1)
		printf("%lld", r.up);//分数的乘法和除法过程可能使分子或分母超过int范围,因此分子和分母应使用long long型来存储。
	else if(abs(r.up) > r.down){
		printf("%d %d/%d", r.up/r.down, abs(r.up)%r.down, r.down);
	}else{
		printf("%d/%d", r.up, r.down);
	}	
}

4、素数

素数的判断

  • 1既不是素数也不是合数。
  • 如果n不能被2、3... ⌊sqrt(n)⌋整除,则n为素数。
//时间复杂度为O(sqrt(n))
bool isPrime(int n){
	if(n <= 1) return false;
	int sqr = (int) sqrt(n * 1.0);
	for(int i = 2; i <= sqrt; i++){
		if(n % i == 0) return false;
	}
}
//如果n不接近int范围上界(n小于10^9)
bool isPrime(int n){
	if(n <= 1) return false;
	for(int i = 2; i * i <= n; i++){
		if(n % i == 0) return false;
	}
}
//把i定义成long long型,就不会溢出了

求素数表

//暴力法
//时间复杂度为O(nsqrt(n))
//适用于n在10^5内
//以下为求100以内的所有素数
#include <cstdio>
#include <math.h>
bool isPrime(int n){
	if(n <= 1) return false;
	int sqrt = (int)sqrt(n * 1.0);
	for(int i = 2; i <= sqrt; i++){
		if(n % i == 0) return false;
	}
	return true;
}
int prime[101], pNum = 0;//素数表,素数个数
bool p[101} = {0};//p[i]=ture表示i是素数
void Find_Prime(){
	for(int i = 1; i < 101){
		if(isPrime(i) == true){
			prime[pNum++] = i;
			p[i] = true;
		}
	}
}
int main(){
	Find_Prime();
	for(int i = 0; i < pNum; i++){
		prinf("%d", prime[i]);
	}
	return 0;
}
//埃氏筛法(Eratosthenes筛法)
//时间复杂度O(nloglogn)
//当从小到大到达数a时,如果a没有被前面筛去,则a一定是素数,再筛去a的倍数。
#include <cstdio>
const maxn = 101;
int prime[maxn], pNum = 0;//素数表,素数个数
bool p[maxn] = {0};用来标记,p[i]=false表示i是素数
void Fine_Prime(){
	for(int i = 2; i < maxn; i++){
		if(p[i] == false){
			prime[pNum++] = i;
			for(int j = i + i; j < maxn; j += i){
				p[j] = true;
			}
		}
	}
}
int main(){
	Find_Prime();
	for(int i = 0; i < pNum; i++){
		prinf("%d", prime[i]);
	}
	return 0;
}

5、质因子

质因子的表示

  • 对于int范围的数来说,fac数组大小只需要开到10就可以了。
struct factor{
	int x, cnt;//质因子,个数
}fac[10];

质因子分解

  • 时间复杂度为O(sqrt(n))
#include <cstdio>
#include <cmath>
const int maaxn = 100010;
bool is_prime(int n){
	if(n == 1) return false;
	int sqrt = (int)sqrt(n * 1.0);
	for(int i = 2; i <= sqrt; i++){
		if(n % i == 0) return false;
	}
	return true;
}
int prime[maxn], pNum = 0;
void Find_Prime(){
	for(int i = 1; i < maxn; i++){
		if(is_prime(i) == true){
			prime[pNum++] = i;
		}
	}
}
struct factor{
	int x,cnt;
}fac[10];
int main(){
	//打表
	Find_Prime();
	int n, num = 0;//记录质因子数组下标
	scanf("%d", &n);
	if(n == 1) printf("1=1");
	else{
		printf("%d=", n);
		int sqrt = (int)sqrt(n * 1.0);
		//枚举1~根号n范围内的所有质因子p,判断p是否为n的因子
		for(int i = 0; i < pNum && prime[i] <= sqrt; i++){
			if(n % prime[i] == 0){
				fac[num].x = prime[i];
				fac[num].cnt = 0;
				while(n % prime[i] == 0){
					fac[num].cnt++;
					n /= prime[i];
				}
				num++;
			}
			if(n == 1) break;
		}
		//循环后n!=1说明当前n为原n的一个大于根号n的质因子
		if(n != 1){
			fac[num].x = n;
			fac[num++].cnt = 1;
		}
		for(int i = 0; i < num; i++){
			if(i > 0) printf("*");
			printf("%d", fac[i].x);
			if(fax[i].cnt > 1){
				printf("^%d", fac[i].cnt);
			}
		}
	}
	return 0;
}

求正整数N的因子个数,只需要对其质因子进行分解,得到各质因子pi的个数分别为e1、e2...ek,则N的因子个数是(e1+1) *(e2+1)*...*(ek+1)。

  • 对每个质因子pi都可以选择其出现0次、1次....ei次,共ei+1种可能,组合起来就是因子个数。

6、大整数运算

大整数(高精度整数),是用基本数据类型无法存储其精度的整数。

大整数的表示

  • 默认小端存储。
  • 如果把整数按字符串%s读入,是大端存储。
struct bign{
	int d[1000];
	int len;
	//初始化
	bign(){
		memset(d, 0, sizeof(d));
		len = 0;
	}
}

大整数的读入 先按字符串%s读入,再将字符串数组逆序存至结构体。

bign change(char str[]){
	bign a;
	a.len = strlen(str);
	for(int i = 0; i < a.len; i++){
		a.d[i] = str[a.len - i - 1] - '0';
	}
	return a;
}

大整数的比较

  • 比较长度。如果不相等,长度较长的大。
  • 如果相等,从高位到低位进行比较。
int compare(bign a, bign b){
	if(a.len > b.len) 
		return 1;
	else if(a.len < b.len) return -1;
	else{
		for(int i = 0; i < a.len; a++){
			if(a.d[i] > b.d[i]) return 1;
			else if(a.d[i] < b.d[i]) return -1;
		}
		return 0;
	}
}

高精度加法

  • a和b都是非负数。
bign add(bign a, bign b){
	bign c;//结果
	int carry = 0;//进位
	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;
}

高精度减法

  • 使用sub函数前要比较两个数的大小,如果被减数小于减数,需要交换两个变量,然后输出负号,再使用sub函数。
bign sub(bign a, bign b){
	bign c;//结果
	for(int i = 0; i < a.len || i < b.len; i++){
		if(a.d[i] < b.d[i]){
			a.d[i+1]--;//借位
			a.d[i] += 10;
		}
		c.d[c.len++] = a.d[i] - b.d[i];
	}
	//最高位下标大于1且最高位为0
	while(c.len - 1 >= 1 && c.d[c.len-1] == 0){
		c.len--;
	}
	return c;
}

7、组合数

【ELSE】

thought

1、two pointers

two pointers是利用问题本身与序列的特性,使用两个下标i、j对序列进行扫描,以较低的复杂度解决问题。

2、打表

打表是一种典型的用空间换时间的技巧,一般指将所有可能需要用到的结果实现计算出来,要使用时可以直接查表获得。

  • 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果。
  • 在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果。
  • 对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律。

3、递推

  • 从左到右遍历字符串,如果当前位i是P,那么leftNumP[i]等于leftNumP[i-1] + 1;如果当前位i不是P,则leftNumP[i]等于leftNumP[i-1]。