(文章框架来自学长,内容来源于自我想象,不足之处dd)
入门篇
在入门篇我们会学习很多有用的知识,下面给出一份比较推荐的学习的路径,大家可以根据自己的情况去查缺补漏。
1. 枚举
2. 模拟
3. 递归
4. 前缀和和差分
5. 尺取法
6. 排序算法原理的理解
7. 二分,三分
1> STL自带的二分函数
在库#include <algorithm
中有两个函数:upper_bound
和lower_bound
。
这两个函数的作用是二分查找一个数在数组中出现的位置。区别是upper
返回第一个大于搜索数的位置,而lower
是第一个大于等于搜索数的位置。
函数的用法:lower_bound(a.begin(),a.end(),x)
– 返回第一个大于等于 x 的数的地址。而由于是地址,在最后要加上−a
(也就是减去地址)。
//vector数组中
int index = lower_bound(vec.begin(), vec.end(), t) - vec.begin();
//普通数组中
int index = lower_bound(a + 1, a + n + 1, x) - a;
2> 手写二分
- 递归形式
int binarySearch(std::vector<int> &nums, int left, int right, int target)
{
//找不到,返回-1
if (left > right)
return -1;
//防止(left + right)溢出
int mid = left + (right - left) / 2;
if (nums[mid] == target)
{
//找到target,返回下标
return mid;
}
else if (nums[mid] < target)
{
//中间的值小于target,说明target在右边
return binarySearch(nums, mid + 1, right, target);
}
else
{
//中间的值大于target,说明target在左边
return binarySearch(nums, left, mid - 1, target);
}
}
//重载binarySearch方法,便于调用
int binarySearch(std::vector<int> &nums, int target)
{
//nums为空则直接返回-1,否则递归查找
return nums.size() == 0 ? -1 : binarySearch(nums, 0, nums.size() - 1, target);
}
- 非递归形式
int binarySearch(std::vector<int> &nums, int target)
{
//nums为空时直接返回-1
if (nums.size() == 0)
return -1;
int left = 0;
int right = nums.size() - 1;
while (left <= right)
{
//防止(left + right)溢出
int mid = left + (right - left) / 2;
if (nums[mid] == target)
{
//找到target,返回下标
return mid;
}
else if (nums[mid] < target)
{
//中间的值小于target,说明target在右边
left = mid + 1;
}
else
{
//中间的值大于target,说明target在左边
right = mid - 1;
}
}
//找不到,返回-1
return -1;
}
8. 分治
- 分治求解三步法:
- **划分问题:**把问题的实例划分成子问题。
- **递归求解:**递归解决子问题。
- **合并问题:**合并子问题的解得到原问题的解。
- 例:归并排序 (merge_sort) –
O(n*logn)
void merge_sort(int *A, int x, int y, int *T){
if(y - x > 1){
int m = x + (y - x) / 2; //划分
int p = x, q = m, i = x;
merge_sort(A, x, m, T);
merge_sort(A, m, y, T); //递归求解
while(p < m || q < y){
if(q >= y || (p < m && A[p] <= A[q])) T[i++] = A[p++]; //从左半数组复制到临时空间
else T[i++] = A[q++]; //从右半数组复制到临时空间
}
for(i = x; i < y; i++) A[i] = T[i]; //从辅助空间复制回A数组
}
}
9. 贪心
10. 简单搜索
11. 读入优化
inline int read_int()//快读整数
{
char c;
int sign = 1;
while ((c = getchar()) < '0' || c > '9')
if (c == '-') sign = -1;
int res = c - '0';
while ((c = getchar()) >= '0' && c <= '9')
res = res * 10 + c - '0';
return res * sign;
}
inline double read_double()//快读实数
{
char c;
int sign = 1;
while((c = getchar()) < '0' || c > '9')
if(c == '-') sign = -1;
double res = c - '0';
while((c = getchar()) >= '0' && c <= '9')
res = res * 10 + c - '0';
if(c != '.') return res * sign;
double ll = 0.1;
while((c = getchar()) >= '0' && c <= '9')
res += (c - '0') * ll, ll *= 0.1;
return res * sign;
}
- stringstream的基本操作
#include <iostream>
#include <cstdio>
#include <sstream>//stringstream的头文件
using namespace std;
int main()
{
string line, word;
while(getline(cin, line))
{
stringstream stream(line);
cout << stream.str() << endl;
while(stream >> word) { cout << word << endl; }
}
// 输入: shanghai no1 school 1989
// 输出: shanghai no1 school 1989
// shanghai
// no1
// school
// 1989
printf("\n");
int val1 = 512, val2 = 1024;
stringstream ss;
ss << "val1: " << val1 << endl << "val2: " << val2 << endl;
// "val1: "此处有空格,字符串流是通过空格判断一个字符串的结束
// 将int类型读入ss,变为string类型
cout << ss.str();
// 输出为: val1: 512
// val2: 1024
printf("\n");
string dump;
int a, b;
ss >> dump >> a >> dump >> b;
// 提取512,1024保存为int类型.当然,如果a,b声明为string类型,那么这两个字面值常量相应保存为string类型
cout << a << " " << b << endl;
// 输出为: 512 1024
printf("\n");
// stringstream不会主动释放内存(或许是为了提高效率),但如果在程序中用同一个流,反复读写大量的数据,将会造成大量的内存消耗,所以此时,需要用stream.str("")适时地清除一下缓冲
stringstream sss;
string s;
sss << "shanghai no1 school";
sss >> s;
cout << "size of stream = " << sss.str().length() << endl;
cout << "s: " << s << endl;
sss.str("");
cout << "size of stream = " << sss.str().length() << endl;
// 输出: size of stream = 19
// s: shanghai
// size of stream = 0
return 0;
}
something
浮点数
浮点型比较大小时,不能用a == b
表示,要用|a - b| < 0.0000001
(或其他精确度)。
float:2^23 = 8388608
,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字
。
double:2^52 = 4503599627370496
,一共16位,同理,double的精度为15~16位
。
Memset函数
0的二进制是
00000000 00000000 00000000 00000000
,取后8位00000000
,初始化后00000000 00000000 00000000 00000000
结果还是0负数在计算机中以补码存储,-1的二进制是
11111111 11111111 11111111 11111111
,取后8位11111111
,则是11111111 11111111 11111111 11111111
结果也是-1
memset(a, 0, sizeof(a));
//初始化为0
memset(a, 255, sizeof(a));
//初始化为-1
不能初始化为1
0x8f
的含义:0x是指16进制,所以16进制的2位可以表示2进制的8位
8则对应
1000
,f则对应1111
;所以每个字节的值就是10001111
众所周知 int 的最高位即32位是表示正负的,第32位为1则为负数,反之为整数
很明显
10001111100011111000111110001111
这个数是个大负数 (-1886417009)
memset(a, 0x7f, sizeof(a));
//初始化为很大的数(略小于0x7fffffff)
memset(a, 0xaf, sizeof(a));
//初始化为很小的数
memset(a, 0x8f, sizeof(a));
//初始化为很大的负数
基础篇
学会了上面的算法后。我们就有了很好的基础帮我们去理解学习下一步的各种算法。在此我一共把算法大概划分成了4个大类,
- 数据结构
- 动态规划
- 图论
- 数学
其中计算几何和博弈论归为数学部分,字符串相关算法归为数据结构。接下来我们对每个部分的基础算法进行了解。
一、基础数据结构
对与基础数据结构,很多都是借助STL实现的,所以希望大家都能熟练掌握STL的用法。
1. 栈
2. 队列
3. 链表
4. 堆
5. 优先队列
6. HASH表
7. 二叉树
8. Trie树
字典树可以看作一棵26叉树。
请看以下题目:
给出n个单词构成的词典,再给出m个字符串。统计有多少个字符串为词典中的单词。
(单词与字符串均为小写字母构成)
以下为用字典树实现的标准程序
#include<bits/stdc++.h>
using namespace std;
int ch[1000000][26];
//ch[i][j]的定义:i号结点的某字母子树的根结点编号。j = int('该字母' - 'a')。当编号为0时,结点不存在。
int main()
{
int n, m;
cin >> n >> m;
//以下为字典树的构造
int i, j, ls, c, tot = 1, u, sum = 0;
char s[1000000];
memset(ch, 0, sizeof(ch));
for(i = 0; i < n; i++)
{
u = 0;
scanf("%s", s);
ls = strlen(s);
for(j = 0; j < ls; j++)
{
c = s[j] - 'a';
if(ch[u][c] == 0)
{
tot++;//tot记录结点总数,用于分配编号
ch[u][c] = tot;
}
u = ch[u][c];//将本次修改的结点用于下次操作,相当于在树上深入了一层
}
}
//以下为在字典树上进行的查找操作
bool b;
for(i = 0; i < m; i++)
{
scanf("%s", s);
ls = strlen(s);
b = true;
u = 0;
for(j = 0; j < ls; j++)
{
c = s[j] - 'a';
if(ch[u][c] != 0)
{
u = ch[u][c];
continue;
}
b = false;
break;
}
for(i = 0; i < 26; i++)//判断字符串是否为完整的单词
{
if(ch[u][i] != 0)
{
b = false;
break;
}
}
if(b)
sum++;
}
cout << sum;
return 0;
}
- MY
/*
给定n个长度不超过10的数字串,问其中是否存在两个数字串S,T,使得S是T的前缀。有多组数据,数组组数不超过40,n<=10000
*/
/*
思路->在构建过程中:
若当前串插入后没有新建任何结点,则当前串一定是之前插入的某个串的前缀
若当前串插入过程中经过某个带有串结尾标记的结点,则之前插入的某个串一定是当前串的前缀
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10010;
int t, n, ans, cnt;
char a[15];
int ch[maxn][15];
bool f[maxn];
inline int read()
{
char c;
int f = 1;
while((c = getchar()) < '0' || c > '9')
if(c == '-') f = -1;
int res = c - '0';
while((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + c - '0';
// res = res * 10 + c - '0';
return res * f;
}
bool insert(char *s)
{
int len = strlen(s);
int u = 1;
bool
int main()
{
t = read();
for(int i = 1; i <= t; i++)
{
cnt = 1;//新的Trie树
ans = false;
memset(ch, 0, sizeof(ch));
memset(f, false, sizeof(f));
n = read();
for(int j = 1; j <= n; j++)
{
scanf("%s", a);
if(insert(a)) ans = true;
}
if(ans) printf("YES");
else printf("NO");
}
return 0;
}
9. set和map的用法
10. 并查集
并查集合并与查询
n个元素,m次操作
p1 = 1时,将p2与p3所在集合合并
p1 = 2时,输出p2与p3是(Y)否(N)在同一集合内
#include<bits/stdc++.h>
using namespace std;
int k, n, m, s, ans, p1, p2, p3;
int fa[10010];
int find(int k)//路径压缩
{
if(fa[k] == k) return k;
return fa[k] = find(fa[k]);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) fa[i] = i;//初始化
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &p1, &p2, &p3);
if(p1 == 1) fa[find(p2)] = find(p3);
else if(find(p2) == find(p3)) printf("Y\n");
else printf("N\n");
}
return 0;
}
11. KMP算法
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000010;
//i + 1 - m + 1
int n, m;
char a[maxn], b[maxn];
int p[maxn];
int ans[maxn], cnt;
inline int read()
{
char c;
int f = 1;
while((c = getchar()) < '0' || c > '9')
if(c == '-') f = -1;
int res = c - '0';
while((c = getchar()) >= '0' && c <= '9')
res = res * 10 + c - '0';
return res * f;
}
void pre()
{
p[1] = 0;
int j = 0;
for(int i = 1; i < m; i++)
{
while(j && b[i + 1] != b[j + 1]) j = p[j];
if(b[i + 1] == b[j + 1]) j++;
p[i + 1] = j;
}
}
void kmp()
{
int j = 0;
for(int i = 0; i < n; i++)//!!!!!!!!从0开始枚举
{
while(j && a[i + 1] != b[j + 1]) j = p[j];
if(a[i + 1] == b[j + 1]) j++;
if(j == m)
{
printf("%d\n", i + 1 - m + 1);
j = p[j];
}
}
}
int main()
{
scanf("%s", a + 1);
scanf("%s", b + 1);
n = strlen(a + 1);
m = strlen(b + 1);
pre();
kmp();
for(int i = 1; i <= m; i++) printf("%d ", p[i]);//前缀
printf("\n");
return 0;
}
12. 单调栈
13. 树状数组
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int dp[100001];
inline int read_int()
{
char c;
int sign = 1;
while ((c = getchar()) < '0' || c > '9')
if (c == '-') sign = -1;
int res = c - '0';
while ((c = getchar()) >= '0' && c <= '9')
res = res * 10 + c - '0';
return res * sign;
}
int lowbit(int x) { return x & (-x); }//取二进制中最低位的1
void add(int x, int d, int n)//单点加法
{
while(x <= n)
{
dp[x] += d;
x += lowbit(x);
}
}
int sum(int q)//区间[1, q]求和
{
int ans = 0;
while(q > 0)
{
ans += dp[q];
q -= lowbit(q);
}
return ans;
}
int main()
{
int t, cnt = 0;
char word[20];
t = read_int();
while(t--)
{
memset(dp, 0, sizeof(dp));
n = read_int();
int m;
for(int i = 1; i <= n; i++)
{
m = read_int();
add(i, m, n);
}
printf("Case %d:\n", ++cnt);
while(scanf("%s", word) && word[0] != 'E')
{
int a = read_int();
int b = read_int();
if(word[0] == 'A') add(a, b, n);//a点加b
else if(word[0] == 'S') add(a, -b, n);//a点减b
else printf("%d\n", sum(b) - sum(a - 1));//询问区间和[a, b]
}
}
return 0;
}
14. 线段树
//线段树
struct SegmentTree {
static const int MAXN = 50005; //最大节点数
//static const int MOD = 1e9 + 7;
LL a[MAXN];
LL addLazy[MAXN * 4], mulLazy[MAXN * 4]; //加法lazy标记、乘法lazy标记
LL sum[MAXN * 4]; //区间和
void update(int k) {
//sum[k] = (sum[k << 1] + sum[k << 1 | 1]) % MOD;
sum[k] = sum[k << 1] + sum[k << 1 | 1];
}
//建树
void build(int k, int l, int r) {
addLazy[k] = 0;
mulLazy[k] = 1;
if(l == r) {
//sum[k] = a[l] % MOD;
sum[k] = a[l];
return ;
}
//int mid = (l + ((r - l) >> 1)) % MOD;
int mid = l + ((r - l) >> 1);
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
update(k);
}
void pushdown(int k, int l, int r) {
//int mid = (l + ((r - l) >> 1)) % MOD;
int mid = l + ((r - l) >> 1);
//维护区间和
//子节点的值 = 此刻子节点的值 * 父结点的乘法lazy标记 + 父结点的加法lazy标记 * 子节点的区间长度
//sum[k << 1] = (sum[k << 1] * mulLazy[k] + addLazy[k] * (mid - l + 1)) % MOD;
sum[k << 1] = sum[k << 1] * mulLazy[k] + addLazy[k] * (mid - l + 1);
//sum[k << 1 | 1] = (sum[k << 1 | 1] * mulLazy[k] + addLazy[k] * (r - mid)) % MOD;
sum[k << 1 | 1] = sum[k << 1 | 1] * mulLazy[k] + addLazy[k] * (r - mid);
//维护lazy标记,修改加法lazy标记时 要遵循乘法优先
//mulLazy[k << 1] = (mulLazy[k << 1] * mulLazy[k]) % MOD;
mulLazy[k << 1] = mulLazy[k << 1] * mulLazy[k];
//mulLazy[k <