未来的一个月我将不定期更新一些我的算法题目,主要是为了本人保研机试做准备。
本人主要熟悉的语言是python,但是由于保研机试基本要求要用C++,所以在未来的一个月中我将去学习一些C++的算法(大部分都是一些经典的算法),以应付机试要求。
本博客代码均为本人手写并AC。
第一次发博客,大佬勿喷。
直接进入正题:
目录
目录
动态规划
动规五部曲:
1.确定dp数组以及下标的含义
2.确定递推公式
3.dp数组初始化
4.确定遍历顺序
5.举例推导dp数组
最长递增子序列长度
//最长递增子序列
//dp数组:dp[i] = max(dp[j] + 1, dp[i]);
int lengthlis(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
vector<int> dp(nums.size(), 1);
int result = 0;
//dp[i]= max(dp[j]+1,dp[i]);
for (int i = 1; i < nums.size(); i++) {
for (int j = 1; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[j] + 1, dp[i]);
}
}
if (result < dp[i]) result = dp[i];
}
return result;
}
最长连续递增子序列长度
动态规划:
注意:这里是连续递增子序列,只需要比较nums[i]和nums[i-1],所以只需要使用一层循环进行遍历即可。
//最长连续递增子序列
// 动态规划
//dp数组:dp[i] = dp[i - 1] + 1;
int lengthOfLIS(vector<int>& nums) {
if (nums.size() == 0) return 0;
//初始化dp数组和最终结果的值
int result = 1;
vector<int> dp(nums.size(), 1);
// 遍历dp数组
for (int i = 1 ; i < nums.size(); i++) {
if (nums[i] > nums[i - 1]) {
dp[i] = dp[i - 1] + 1;
}
//选出dp数组中最大的值
if (dp[i] > result) result = dp[i];
}
return result;
}
贪心算法:
这道题目也可以用贪心来做,也就是遇到nums[i] > nums[i - 1]的情况,count就++,否则count为1(从头开始),记录count的最大值就可以了。
//最长连续递增子序列
// 贪心算法
//dp数组:dp[i] = dp[i - 1] + 1;
int lengthOfLIS(vector<int>& nums) {
if (nums.size() == 0) return 0;
//初始化dp数组和最终结果的值
int result = 1;
int count = 1;
// 遍历给定数组
for (int i = 1; i < nums.size(); i++) {
if (nums[i] > nums[i - 1]) {
count += 1;
}
else
{
//不连续,从头开始
count = 1;
}
//选出dp数组中最大的值
if (count>result)
{
result = count;
}
}
return result;
}
不连续递增子序列的跟前0-i 个状态有关,连续递增的子序列只跟前一个状态有关。
最长递增子序列的个数
//最长连续递增子序列个数
// 动态规划
//dp数组:dp[i] = dp[i - 1] + 1;
//count数组更新分两种情况
int findNumberOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
//初始化dp数组和count数组
vector<int> dp(nums.size(), 1);
vector<int> count(nums.size(), 1);
//定义最大子序列长度
int maxcount = 0;
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
//更新count数组
if (dp[j] + 1 > dp[i]) {
//dp[i] = dp[j] + 1;
count[i] = count[j];
}
else if (dp[j] + 1 == dp[i])
{
count[i] += count[j];
}
//更新dp数组
dp[i] = max(dp[i], dp[j] + 1);
}
//选出最大子序列长度
if (maxcount < dp[i]) maxcount = dp[i];
}
}
int result = 0;
//选出长度等于最大子序列长度的子串的个数
for (int i = 0; i < nums.size(); i++) {
if (maxcount == dp[i]) result += count[i];
}
return result;
}
得出最大连续子序列个数的另一个思路是:跟上面求出最长递增子序列长度一样求出dp数组,然后找出dp数组中值最大的数的个数。这个想法具体没有写代码,但是感觉是对的,我也找了几个测试用例把dp数组打印出来对比了一下。
最长连续公共子序列
这道题也可以使用动态规划思想去写,但是下面是一个偏暴力的方法,这个方法适合新手练习。主要的想法就是选取两个字符串中的一个,然后遍历这个串的所有子串,然后用find()方法在第二个子串中查找是否有该子串,最终找到最长连续公共子序列。
#include<iostream>
using namespace std;
int main() {
int ans_1 = 0;
string s1, s2,ans_2 = "";
//string s1 = "abcde";
cin >> s1 >> s2;
string res = "";
for (int i = 0; i < s1.length()-1; i++) {
for (int j = i+1; j <s1.length(); j++) {
string s3 = "";
for (int k = i; k <=j ; k++) {
s3 += s1[k];
}
//cout << s3 << endl;
if (s3.length() >= ans_2.length()) {
if (s2.find(s3) != -1 && s3.length() <= s2.length()) {
ans_1 = s3.length();
ans_2 = s3;
}
}
}
}
cout << ans_1<<endl;
cout << ans_2 << endl;
return 0;
}
图论
并查集
例题1:剑指 Offer II 116. 省份数量 - 力扣(LeetCode)
//用来查找当前节点的根节点
//利用递归的思想
int Find(vector<int>& parent, int index) {
if (parent[index] != index) {
parent[index] = Find(parent, parent[index]);
}
return parent[index];
}
//将两个根节点进行合并
void Union(vector<int>& parent, int index1, int index2) {
int x = Find(parent, index1);
int y = Find(parent, index2);
parent[x] = y;
}
//主函数
int findCircleNum(vector<vector<int>>& isConnected) {
int cities = isConnected.size();
//用来保存当前节点的父节点
vector<int> parent(cities);
//初始化parent,表示当前节点的父节点是本身
for (int i = 0; i < parent.size(); i++) {
parent[i] = i;
}
//遍历邻接矩阵,如果两个节点相连就将两个节点的根节点合并
for (int i = 0; i < cities; i++) {
for(int j = 0; j < cities; j++ ){
if (isConnected[i][j] == 1) {
Union(parent, i, j);
}
}
}
//求出不相连的根节点的个数
int provinces = 0;
for (int i = 0; i < parent.size(); i++) {
if (parent[i] == i) {
provinces += 1;
}
}
return provinces;
}
跟上一题思路相同,求出不相连的根节点的个数,然后减1就得到了需要添加的路的个数。在写的过程中需要注意循环遍历的次数,以及动态分配parent内存的大小,不然会报错。这一题也可以使用广度优先搜索和深度优先搜索解题,但是目前我还没学到这里,之后会补上的。
#include<iostream>
#include <vector>
using namespace std;
int Find(vector<int> &parent, int index) {
if (parent[index] != index) {
parent[index] = Find(parent, parent[index]);
}
return parent[index];
}
void Union(vector<int>&parent, int index1, int index2) {
int x = Find(parent, index1);
int y = Find(parent, index2);
parent[x] = y;
}
int main() {
int n, m = 0;
cin >> n>>m;
vector<int> parent(n+1);
for (int i = 1; i <= parent.size(); i++) {
parent[i] = i;
}
for (int i = 0; i < m; i++) {
int a, b = 0;
cin >> a >> b;
Union(parent, a, b);
}
int cnt = 0;
for (int i = 1; i < parent.size(); i++) {
if (parent[i] == i) {
cnt += 1;
}
}
cout<<cnt-1<<endl;
}
二分法
二分法的两个模板:
如果l=mid,则mid=(l+r+1)/2,使用模板一;
如果r=mid,则mid=(l+r)/2,使用模板二;
整数序列
方法1:使用暴力的方法。大致思想就是先从1遍历到输入的整数作为首项,然后再遍历等差数列的数目n,使用求和公式:sn=a1n+(n*(n-1))/2即可求出。这个方法可以求出结果,但是有些用例会超时。都使用暴力了,还要什么自行车。
#include<iostream>
using namespace std;
int main() {
int m;
cin >> m;
int i = 1;
int sign = 0;
for (i = 1; i < m; i++) {
for (int j = 2; j < (m / 2); j++) {
if ((i * j + (j * (j - 1)) / 2) == m) {
int a = i;
sign = 1;
for (int k = 1; k <= j; k++) {
cout << a<<" ";
a += 1;
}
cout << endl;
}
}
}
if (sign==0) {
cout << "NONE";
}
return 0;
}
方法2:使用二分法。这次使用的是等差数列的另一个求和公式sn=(a1+an)n/2。遍历首项,然后使用二分的方法找出尾项,最后输出结果。需要注意的是存储数据使用的类型,不然会溢出。
#include<iostream>
using namespace std;
//注意数据的类型
using ll = long long;
int main() {
//二分法
ll N;
cin >> N;
//设置信号量,用来判断是否有结果输出
int sign = 0;
//使用二分法寻找等差数列的最后一项
for (int i = 1; i <=N / 2; i++) {
ll l = i + 1, r = N / 2 + 1;
while (l < r) {
ll mid = (l + r + 1) / 2;
//等差数列的求和公式
ll x = (i + mid) * (mid - i + 1) / 2;
if (x <= N) l = mid;
else r = mid - 1;
}
//对结果进行打印输出
if ((i + r) * (r - i + 1) / 2 == N) {
sign = 1;
for (int j = i; j <= r; j++) {
cout << j << ' ';
}
cout << endl;
}
}
if (sign == 0) {
cout << "NONE";
}
return 0;
}
数的范围
#include<iostream>
#include<vector>
using namespace std;
int n, m;
const int N = 100010;
int q[N];
//vector<int> q(n+10);
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> q[i];
while (m--)
{
int x;
cin >> x;
int l=0, r=n-1;
while (l < r) {
int mid = l + r >> 1;
if (q[mid] >= x) r = mid;
else l = mid + 1;
}
if (q[l] != x) cout << "-1 -1" << endl;
else
{
cout << l << ' ';
int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (q[mid] <= x) l = mid;
else r = mid - 1;
}
if (q[l] != x) cout << "-1 -1" << endl;
cout << l << endl;
}
}
return 0;
}
筛素数
分解质因数
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
//分解质因数
int main() {
int n;
cin >> n;
int temp = n;
for (int i = 2; i < n; i++)
{
while (temp%i==0)
{
cout << i << ' ';
temp /= i;
}
}
return 0;
}
筛质数
埃氏筛
定理:一个质数*任何一个不为1的正整数=合数
埃氏筛的基本思想:
假设有一个筛子存放1~N,例如:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 .........N
先将 2的倍数 筛去:
2 3 5 7 9 11 13 15 17 19 21..........N
再将 3的倍数 筛去:
2 3 5 7 11 13 17 19..........N
之后,再将5的倍数筛去,再来将7的质数筛去,再来将11的倍数筛去........,如此进行到最后留下的数就都是质数,这就是Eratosthenes筛选方法(Eratosthenes Sieve Method)。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000000;
int prime[1000000];
bool notprime[1000000];
int cnt = 0;
//筛质数(埃氏筛)
int main() {
int n;
cin >> n;
notprime[1] = true;
for (int i = 2; i <= n; i++)
{
if (!notprime[i]) {
cnt += 1;
prime[cnt] = i;
}
for (int j = i*i; j <= n; j+=i)
{
notprime[j] = true;
}
}
for (int i = 1; i <= n; i++)
{
cout << prime[i]<<' ';
}
return 0;
}
线性筛
线性筛法(又称欧拉筛法)
从小到大枚举每个数
1.如果当前数没划掉,必定是质数,记录该质数
2.枚举已记录的质数 (如果合数已越界则中断)
(1) 合数未越界,则划掉合数
(2)条件 i%p==0,保证合数只被最小质因子划掉
若i是质数,则最多枚举到自身中断
若i是合数,则最多枚举到自身的最小质数中断
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=100000;
int prime[N];
bool notprime[N];
int cnt;
//线性筛
int main() {
int n;
cin >> n;
notprime[1] = true;
for (int i = 2; i <= n; i++) {
if (!notprime[i]) {
prime[++cnt] = i;
}
for (int j = 1; i*prime[j] <= n; j++)
{
notprime[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
for (int i = 1; i < n; i++)
{
cout << prime[i] << ' ';
}
return 0;
}
做个例题吧
这里涉及到数组越界的问题,需要注意。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 100000000;
ll prime[N];
bool notprime[N];
int cnt = 0;
int main() {
ll n = 10000000;
notprime[1] = true;
for (ll i = 2; i <= n; i++)
{
if (!notprime[i]) {
prime[++cnt] = i;
}
for (ll j = (ll)i * i; j <= n; j += i) {
notprime[j] = true;
}
}
int a, b;
while (cin >> a >> b) {
//for (int i = 0; i <= b; i++) {
// cout << prime[i] << ' ';
//}
int cnt2 = 0;
for (int i = 0; i < n; i++)
{
if (prime[i] > b)
{
break;
}
if (prime[i] >= a) {
cnt2++;
}
}
if (a == 1) cout << b - a - cnt2 << endl;
else cout << b - a + 1 - cnt2 << endl;
}
return 0;
}
DFS
全排列
例题:
给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 10;
int path[N];
bool st[N];
int n;
void dfs(int u) {
if (u == n) {
for (int i = 0; i < n; i++)
{
cout << path[i] << ' ';
}
cout << endl;
}
for (int i = 1; i <= n; i++)
{
if (!st[i]) {
path[u] = i;
st[i] = true;
dfs(u + 1);
path[u] = 0;
st[i] = false;
}
}
}
int main() {
cin >> n;
dfs(0);
return 0;
}
N皇后问题
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 10;
char g[N][N];
int col[N], dg[N], udg[N];
int n;
void DFS(int u) {
if (u == n) {
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
cout << g[i][j] << ' ';
}
cout << endl;
}
cout << endl;
}
for (int i = 0; i < n; i++)
{
if (!col[i] && !dg[u+i] && !udg[n-u+i]) {
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = true;
DFS(u + 1);
col[i] = dg[u + i] = udg[n - u + i] = false;
g[u][i] = '.';
}
}
}
int main() {
cin >> n;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
g[i][j] = '.';
DFS(0);
return 0;
}
递进数字
暴力解法(直接枚举),会有部分用例超时。
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
int main() {
int T;
cin >> T;
int l, r;
while (cin >> l >> r)
{
int cnt = 0;
for (int i = l; i <= r; i++) {
string s = to_string(i);
if (s.length() >= 2) {
int j = 0;
for (j = 0; j < s.length() - 1; j++)
{
if ((s[j + 1] - s[j]) == 1 || (s[j] - s[j + 1]) == 1) {
continue;
}
else
{
break;
}
}
if (j >= (s.length() - 1)) cnt += 1;
}
}
cout << cnt << endl;
}
return 0;
}
树
二叉树的遍历
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<string>
#include<string.h>
using namespace std;
string pre, in;
void dfs(string pre, string in) {
if (pre == "") return;
char root = pre[0];
int k = in.find(root);
dfs(pre.substr(1,k), in.substr(0,k));
dfs(pre.substr(k+1), in.substr(k+1));
cout << root;
}
int main() {
while (cin >> pre >> in)
{
dfs(pre, in);
cout << endl;
}
return 0;
}
图
图的存储
邻接表法(模板)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
//存储邻接表中的值
int e[N];
//存储邻接表的头
int h[N];
//存储邻接表中的元素的下一个坐标
int ne[N];
//存储当前元素的位置
int index;
//邻接表中添加元素
int add(int a, int b) {
e[index] = b;
ne[index] = h[a];
h[a] = index;
index++;
}
int main() {
//初始化所有头部为-1
memset(h, -1, sizeof h);
return 0;
}
图的深度优先遍历
模板
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010, M = N * 2;
int n, m;
int e[N], h[N], ne[N];
bool st[N];
int index;
void add(int a, int b) {
e[index] = b;
ne[index] = h[a];
h[a] = index;
index++;
}
void dfs(int u) {
//表示被访问过
st[u] = true;
for (int i = h[u]; i !=-1 ; i=ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
int main() {
memset(h, -1, sizeof h);
dfs(1);
return 0;
}
树的重心
例题:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<string>
using namespace std;
const int N = 100010, M = N * 2;
int h[N], e[M], ne[M];
int st[N];
int n, m;
int index;
int ans = N;
void add(int a, int b) {
e[index] = b;
ne[index] = h[a];
h[a] = index;
index++;
}
//以u为根节点的子树的点的数量
int dfs(int u) {
st[u] = true;
int sum = 1, res = 0;
for (int i = h[u]; i != -1; i=ne[i])
{
int j = e[i];
if (!st[j]) {
int s = dfs(j);
res = max(res, s);
sum += s;
}
}
res = max(res, n - sum);
ans = min(res, ans);
return sum;
}
int main() {
//初始化头节点
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i < n; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1);
cout << ans << endl;
return 0;
}
拓扑排序
#include<iostream>
#include<algorithm>
#include<math.h>
const int N = 100010;
using namespace std;
int h[N], e[N], ne[N],idx;
int q[N], d[N];
int n, m;
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
bool topsort() {
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++)
{
if (d[i] == 0) {
q[++tt] = i;
}
}
while (hh <= tt) {
int t = q[hh++];
for ( int i = h[t]; i !=-1; i = ne[i])
{
int j = e[i];
d[j]--;
if (d[j] == 0) q[++tt] = j;
}
}
if (tt == n - 1) return true;
else return false;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
d[b]++;
}
if (topsort()==true) {
for (int i = 0; i < n; i++)
{
cout << q[i] << ' ';
}
}
else puts("-1");
return 0;
}
使用STL版本:
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N],idx;
int d[N];
queue<int> q;
vector<int> res;
int n, m;
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
bool topsort() {
for (int i = 1; i < n; i++)
if (d[i] == 0) q.push(i);
while (!q.empty())
{
int t = q.front();
res.push_back(t);
q.pop();
for (int i = h[t]; i !=-1; i=ne[i])
{
int j = e[i];
d[j]--;
if (d[j] == 0) q.push(j);
}
}
if (res.size() == n) return true;
else false;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
d[b]++;
}
if (topsort()) {
for (auto i : res) {
cout << i << ' ';
}
}
else cout << "-1" << endl;
return 0;
}
基本算法
快速排序
#include<iostream>
#include<vector>
using namespace std;
void quick_sort(vector<int> &q,int l,int r) {
if (l >= r) return;
int x = q[l], i = l, j = r ;
while (i<j) {
while (q[i] < x) i++;
while (q[j] > x) j--;
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
int main() {
//待排序序列个数
int n;
cin >> n;
vector<int> q(n);
for (int i = 0; i < n; i++) cin >> q[i];
quick_sort(q ,0 ,n-1);
for (int i = 0; i < q.size(); i++) cout << q[i]<<' ';
return 0;
}
拓扑排序
宽搜应用
#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N], idx;
int q[N], d[N];
int n, m;
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
bool topsort() {
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++)
if (d[i] == 0)
q[++tt] = i;
while (hh<=tt)
{
int t = q[hh++];
for (int i = h[t]; i != -1; i=ne[i])
{
int j = e[i];
d[j]--;
if (d[j] == 0) q[++tt] = j;
}
}
if (tt = n - 1) return true;
else false;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
d[b]++;
}
if (topsort()) {
for (int i = 0; i < n; i++) cout << q[i] << ' ';
}
else cout << "-1" << endl;
return 0;
}
宽搜应用-使用队列库
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N = 100010;
int h[N], e[N], ne[N],idx;
int d[N];
queue<int> q;
vector<int> res;
int n, m;
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
bool topsort() {
for (int i = 1; i < n; i++)
if (d[i] == 0) q.push(i);
while (!q.empty())
{
int t = q.front();
res.push_back(t);
q.pop();
for (int i = h[t]; i !=-1; i=ne[i])
{
int j = e[i];
d[j]--;
if (d[j] == 0) q.push(j);
}
}
if (res.size() == n) return true;
else false;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
d[b]++;
}
if (topsort()) {
for (auto i : res) {
cout << i << ' ';
}
}
else cout << "-1" << endl;
return 0;
}
堆排序
构造堆:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 100010;
int heap[N];
//代表堆的长度
int n;
//表示当前元素的位置
int index;
//构建小顶堆
//从当前结点开始,和它的父节点比较:
//若比父节点小则交换,然后将当前节点下标更新为原父节点下标;
//否则退出
void shift_up(int i) {
while (i>1 && heap[i]<heap[i>>1]){
swap(heap[i], heap[i >> 1]);
i >>= 1;
}
}
//当前节点与其左右孩子(如果有的话)中较小者作比较:
//若后者比父节点小则交换,并更新当前节点下标为被交换的孩子节点下标;
//否则退出
void shift_down(int i) {
while ((i << 1)<=index)
{
int j = i << 1;//j=2i
if (j < n && heap[j + 1] < heap[j]) j++;
if (heap[i] > heap[j])swap(heap[i], heap[j]);
else break;
i = j;
}
}
void push(int x) {
heap[++index] = x;
shift_up(index);
}
//弹出堆顶元素
void pop() {
heap[1] = heap[index--];
shift_down(1);
}
//返回堆顶元素
int top() {
return heap[1];
}
int main() {
memset(heap, 0, sizeof heap);
return 0;
}
算法步骤为:
- 构造初始堆。从最后一个非叶子结点
arr[n/2]
开始,自下而上进行下沉操作;- 将堆顶元素与末尾元素交换,此时的末尾元素从堆中排除,然后再次下沉根节点;
- 反复执行步骤 2,直到整个序列有序。
堆排序:
void shift_down(int* arr, int i, int n) {
while ((i << 1) <= n) {
int j = i << 1;
if (j < n && arr[j+1] > arr[j]) j++;
if (arr[i] < arr[j]) swap(arr[i], arr[j]);
else break;
i = j;
}
}
void heap_sort(int* arr, int n) {
// init heap
for (int i = n >> 1; i >= 1; i--)
shift_down(arr, i, n);
// shift down from bottom to top
while (--n) {
swap(arr[1], arr[n+1]);
shift_down(arr, 1, n);
}
}
前缀和
前缀和的作用是求数组某一区间的和
题目:
#include<iostream>
#include<vector>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int n, m;
cin >> n >>m;
//输入数组
vector<int> a(n + 1);
for (size_t i = 1; i <= n; i++) cin >> a[i];
//求前缀和数组
vector<int> s(n + 1);
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
while (m--)
{
int l, r;
cin >> l >> r;
//求区间和
int res = s[r] - s[l - 1];
cout << res << endl;
}
return 0;
}
前缀和(二维)
#include<iostream>
#include<vector>
using namespace std;
int n,m;
int main() {
ios::sync_with_stdio(false);
cin >> n >> m;
vector<vector<int>> a(n + 1);
vector<vector<int>> s(n + 1);
for (int i = 0; i <= n; i++) a[i].resize(m + 1);
for (int i = 0; i <= n; i++) s[i].resize(m + 1);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
//求前缀和数组
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
int res = 0;
//算子矩阵的和
res = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
cout << res << endl;
return 0;
}
前缀和求每个字母的个数
#include<iostream>
#include<algorithm>
#include<map>
#include<cstring>
#include<string>
using namespace std;
const int N = 1000010;
int s[26][N];
char str[N];
int main() {
ios::sync_with_stdio(false);
cin >> str+1;
for (int i = 0; i < 26; i++)
{
for (int j = 1; str[j]; j++)
{
if (str[j] - 'a' == i) {
s[i][j] = s[i][j - 1] + 1;
}
else
{
s[i][j] = s[i][j - 1];
}
}
}
int Q;
cin >> Q;
while (Q--)
{
int a, b, c, d;
cin >> a >> b >> c >> d;
int same = 1;
for (int i = 0; i < 26; i++)
{
if (s[i][b] - s[i][a-1] != s[i][d] - s[i][c-1]) {
same = 0;
break;
}
}
if (same==1) cout << "DA" << endl;
else cout << "NE" << endl;
}
return 0;
}
差分
差分常用来:对于给定的前缀和数组,使得前缀和数组的某一区间里面全部加上某个数。
#include<iostream>
#include<vector>
using namespace std;
int n;
void insert(vector<int> &b,int l, int r, int c) {
b[l] += c;
b[r + 1] -= c;
}
int main() {
cin >> n;
vector<int> a(n + 2);
vector<int> b(n + 2);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 0; i <= n; i++)
insert(b, i, i, a[i]);
for (int i = 1; i <= n; i++) cout << b[i] << ' ';
int l, r,c;
cin >> l >> r >> c;
insert(b, l, r, c);
for (int i = 1; i <= n; i++)
{
b[i] += b[i - 1];
}
for (int i = 1; i <= n; i++) cout << b[i] << ' ';
return 0;
}
#include<iostream>
#include<math.h>
#include<math.h>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 10010;
int vis[N];
int n, m, k;
int main() {
while (cin >> n >> m >> k)
{
memset(vis, 0, sizeof vis);
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
vis[x - k]++;
vis[x + k + 1]--;
}
//求前缀和
for (int i = 1; i <= n; i++) vis[i] += vis[i - 1];
int res = 0;
int p = 2 * k + 1;
for (int i = 1; i <= n; i++)
{
int len = 0;
while (vis[i] == 0 && i <= n) {
len++;
i++;
}
if (len != 0) res += ceil((double)len / p);
}
cout << res << endl;
}
return 0;
}
双指针算法
常用模板:
for(int i=0,j=0;i<n;i++){
while(j<i&&check(i,j)) j++;
//每道题的逻辑
}
核心思想是将暴力算法优化到O(n)
#include<iostream>
#include<string>
using namespace std;
//问题描述:输入几个单词,用空格隔开,然后输出每个单词,每行一个单词
int main() {
char ss[1000];
gets_s(ss);
int n = strlen(ss);
for (int i=0; i < n; i++)
{
int j = i;
while (j < n && ss[j] != ' ')j++;
for (int k = i; k <= j; k++)cout << ss[k];
cout << endl;
i = j;
}
return 0;
}
最长连续不重复子序列
#include<iostream>
#include<vector>
using namespace std;
int n;
int main() {
cin >> n;
vector<int> a(n+2);
vector<int> s(n+2);
for (int i = 0; i < n; i++) cin >> a[i];
int res = 0;
for (int i = 0,j=0; i < n; i++)
{
s[a[i]]++;
while (j < i && s[a[i]]>1) {
s[a[j]]--;
j++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
位运算
题目描述:输入n,k,输出n的二进制表示中第k位(k位是从右往左第k位)。
#include<iostream>
using namespace std;
//方法一
int main() {
int n,k;
cin >> n>>k;
int res = n >> k & 1;
cout << res << endl;
return 0;
}
//方法二
int main() {
int n, k;
cin >> n >> k;
int a = n >> k & (-n);
cout << a <<endl;
return 0;
}
离散化
将1 ,2 ,6,56,457,2355……这种数组映射到1,2,3,4,5,6……上去。
(懒得敲了)
全排列
使用STL中的algorithm库
数据结构
单链表
#include<iostream>
#include<vector>
using namespace std;
const int N = 100010;
//val用于存储节点中的值
int val[N];
//Next用于存储当前节点的下一个节点坐标
int Next[N];
//head表示链表的头部,起始为空
int head;
//index用于存储当前访问的节点位置
int index;
//链表初始化
void init() {
head = -1;
index = 0;
}
//在头节点后面添加元素
void add_to_head(int x) {
val[index] = x;
Next[index] = head;
head = index;
index++;
}
//在任意位置的后面添加元素
void add(int k,int x) {
val[index] = x;
Next[index] = Next[k];
Next[k] = index;
index++;
}
//移除链表中k节点后面的一个节点
void remove(int k) {
if (k == -1) head = Next[head];
Next[k] = Next[Next[k]];
}
int main() {
int m;
cin >> m;
init();
while (m--)
{
char op;
cin >> op;
int k, x;
if (op == 'H') {
cin >> x;
add_to_head(x);
}
else if (op == 'D') {
cin >> k;
remove(k-1);
}
else if (op == 'I') {
cin >> k >> x;
add(k-1, x);
}
}
for (int i = head; i != -1; i = Next[i]) cout << val[i] << ' ';
cout << endl;
return 0;
}
双链表
#include<iostream>
#include<vector>
using namespace std;
const int N = 10010;
int e[N], l[N], r[N];
//e数组用来存储节点内存储的元素
//l数组用来存储当前节点中的前一个节点的位置
//r数组用来存储当前节点中的后一个节点的位置
//index用来保存当前节点的位置
int index;
//这里省去头节点和尾节点
//使用0作为头部
//使用1作为尾部
//初始化双链表
void init() {
r[0] = 1;
l[1] = 0;
index = 2;
}
//在双链表中第k个位置后添加一个元素
void add(int k,int x) {
e[index] = x;
r[index] = r[k];
l[index] = k;
l[r[k]] = index;
r[k] = index;
}
//删除双链表中第k个位置
void remove(int k) {
l[r[k]] = l[k];
r[l[k]] = r[k];
}
int main() {
return 0;
}
栈
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
//stk数组为模拟栈
int stk[N];
//tt表示当前栈顶指针
int tt;
//插入栈顶元素
void my_push(int x) {
stk[++tt] = x;
}
//弹出栈顶元素并返回
int my_pop() {
return stk[tt--];
}
//判断栈是否为空
int is_empty() {
if (tt > 0) return 1;
else return 0;
}
int main() {
return 0;
}
队列
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
//q 数组用来模拟队列
int q[N];
//hh,tt分别代表头指针和尾指针
int hh, tt;
//在队尾插入元素
void my_push(int x) {
q[++tt] = x;
}
//在队头弹出元素
int my_pop() {
return q[hh++];
}
//判断队列是否为空
int is_empty() {
if (hh <= tt) return 1;
else return 0;
}
int main() {
return 0;
}
单调栈
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int stk[N];
int main() {
ios::sync_with_stdio(false);
int n;
cin >> n;
int tt = 0;
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
while (tt != 0 && stk[tt] >= x) tt--;
if (tt) cout << stk[tt] << ' ';
else cout << -1 << ' ';
stk[++tt] = x;
}
return 0;
}
单调队列
#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N = 100010;
int q[N], a[N];
int n, k;
int main() {
scanf_s("%d%d", &n, &k);
for (int i = 0; i < n; i++) scanf_s("%d", &a[i]);
int hh = 0, tt = -1;
for (int i = 0; i < n; i++)
{
//判断队头指针是否已经划出滑动窗口
if (hh <= tt && i - k + 1 > q[hh])hh++;
while (hh <= tt && a[q[tt]] >= a[i]) tt--;
q[++tt] = i;
if (i >= k - 1) cout<<a[q[hh]]<<' ';
}
puts("");
hh = 0; tt = -1;
for (int i = 0; i < n; i++)
{
//判断队头指针是否已经划出滑动窗口
if (hh <= tt && i - k + 1 > q[hh])hh++;
while (hh <= tt && a[q[tt]] <= a[i]) tt--;
q[++tt] = i;
if (i >= k - 1) cout<< a[q[hh]]<<' ';
}
return 0;
}
KMP
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int n, m;
char s[N], p[N];
int ne[N];
int main() {
cin >> n >> p + 1 >> m >> s + 1;
//求next过程
for (int i = 2,j = 0; i <= n; i++)
{
while (j && p[i] != p[j + 1])j = ne[j];
if (p[i] == p[j + 1]) j++;
ne[i] = j;
}
//匹配过程
for (int i = 1,j=0; i <= m; i++)
{
while (j && s[i] != p[j + 1])j = ne[j];
if (s[i] == p[j + 1]) j++;
if (j == n) {
//匹配成功
cout << i - n << ' ';
j = ne[j];
}
}
return 0;
}
思维题
#include<iostream>
#include<algorithm>
#include<math.h>
#include<map>
using namespace std;
const int N = 100010;
int d, n;
int K[N], V[N];
map<int,int> mp;
int main() {
cin >> d;
cin >> n;
double t = 0;
int max_k = 0, min_k = 10000000;
for (int i = 0; i < n; i++)
{
int k, v;
cin >> k >> v;
mp[k] = v;
t = max(t, ((double)d - k) / mp[k]);
}
printf("%.6f", (double)(d / t));
return 0;
}