1、memset
按照字节对内存块进行初始化,注意只能填充0或-1
memset(a,-1,sizeof(a));
2、fill
可以填充任意数字
fill(arr, arr + 10, 2);
3、求解区间[x, y]之和满足为k的倍数的数量
前缀和加上排列组合
#include <iostream>
using namespace std;
int n, k, ans, a;
long long sum[100010], dp[100010];
int main() {
cin >> n >> k;
for(int i = 1; i <= n; ++i){
cin >> a;
sum[i] = (sum[i - 1] + a) % k;
dp[sum[i]]++;
}
for(int i = 0; i < k; ++i)
// 若某两个前缀和求余后相等
// 则任选两个数,取区间(x, y]即可使得这个区间之和求余满足题目要求
ans += dp[i] * (dp[i] - 1) / 2;
// 最后加上区间中元素个数为1的情况
cout << ans + dp[0];
return 0;
}
4、find函数
函数作用:查找该元素在数组中第一次出现的位置的地址(类似于0x的地址)
模型:find(查找起点,查找终点,查找目标元素)
同样,查找区间为[查找起点,查找终点)
字符串查找时,若未找到,则返回-1;
数组查找时,若未找到,则返回数组最后一个元素
#include <bits/stdc++.h>
using namespace std;
int main() {
// 字符串查找
string s = "oihfoadfgh";
cout << s.find('a') << endl;
// 数组查找
int a[10] = {2, 6, 8, 1, 3, 7, 5, 1, 0, 11};
// 打印类似0x地址
cout << find(a, a + 5, 8) << endl;
// 打印数组【】内的序号
cout << find(a, a + 5, 8) - a << endl;
return 0;
}
5、 PI(Π)
double PI = atan(1.0) * 4;
6、数据范围
int的范围大概为:2e9
long long的范围大概为:9e18
7、读取一行字符串
getline(cin, s);
8、判断某个字符是否是字母
bool flag = isalpha(s[i]);
9、判断某个数是否是回文数
//是否回文
bool is_pali(int x) {
string str = to_string(x);
string s = str;
reverse(s.begin(), s.end());
return s == str;
}
10、卡片换位
空格也可以代表一个人物(这里将他命名为K),这个人物的特殊之处在于他可以与周围的任何一个人交换位置
那么我们在最开始的时候就记录几个特殊点的坐标:A关羽,B张飞,K空格,就能够设置搜索的边界了
那么进行搜索时总要有一个开始点,不妨我们选择让空格主动出击,主动去和周围的人物交换,进行上下左右的尝试
每dfs一次就是对华容道阵容的一次更新
#include <bits/stdc++.h>
using namespace std;
// 设置成最大值
int Min = INT_MAX;
// 结构体定义A,B,空格坐标
struct
{
int x, y;
}a, b, k;
//记忆化搜索
int vis[3][3][3][3][3][3];
//上下左右走
int Next[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
void dfs(int x1, int y1, int x2, int y2, int x, int y, int step) {
//边界1,走不出去了,所走的步数已经比求得的最小值大,再走下去也没意义
if (step > Min)
return;
//边界2,交换完成
if (x1 == b.x && y1 == b.y && x2 == a.x && y2 == a.y) {
Min = min(step, Min);
return;
}
//边界3,走出迷宫边界
if (x < 0 || x > 1 || y < 0 || y > 2)
return;
// 边界4,已经搜索过这种情况了
if (vis[x1][y1][x2][y2][x][y] == 1)
return;
vis[x1][y1][x2][y2][x][y] = 1;
// 开始dfs主干部分
// 四个方向搜索
for (int i = 0; i < 4; i++) {
int tx = x + Next[i][0];
int ty = y + Next[i][1];
if (tx == x1 && ty == y1)//空格与A交换位置
dfs(x, y, x2, y2, x1, y1, step + 1);
else if (tx == x2 && ty == y2)//空格与B交换位置
dfs(x1, y1, x, y, x2, y2, step + 1);
else//空格与*交换位置
dfs(x1, y1, x2, y2, tx, ty, step + 1);
}
vis[x1][y1][x2][y2][x][y] = 0;
}
int main() {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
char s = getchar();
if (s == 'A') {
a.x = i;
a.y = j;
}
if (s == 'B') {
b.x = i;
b.y = j;
}
if (s == ' ') {
k.x = i;
k.y = j;
}
}
getchar();
}
dfs(a.x, a.y, b.x, b.y, k.x, k.y, 0);
printf("%d", Min);
return 0;
}
11、人物相关性分析
将每个Alice、Bob的位置找出来,然后在需要的范围内进行遍历来查找两者同时出现的次数
#include <iostream>
#include <vector>
using namespace std;
int k;
string s;
long long ans;
int main() {
cin >> k >> s;
vector<int> Alice, Bob;
for (int i = 0; i < s.size();) {
if (s.substr(i, 5) == "Alice") {
if (i - 1 >= 0 && isalpha(s[i - 1]) || i + 5 < s.size() && isalpha(s[i + 5]))
continue;
Alice.push_back(i);
i += 5;
}
else if (s.substr(i, 3) == "Bob") {
if (i - 1 >= 0 && isalpha(s[i - 1]) || i + 3 < s.size() && isalpha(s[i + 3]))
continue;
Bob.push_back(i);
i += 3;
}
else i++;
}
for (int i = 0, l = 0, r = 0; i < Alice.size(); i++) {
//维护窗口左边界
while (l < Bob.size() && Bob[l] < Alice[i] - k - 3)
l++;
//维护窗口右边界
while (r < Bob.size() && Bob[r] < Alice[i] + k + 5)
r++;
ans += r - l;
}
cout << ans;
return 0;
}
12、文件
freopen("D:\\desktop\\input.txt","r",stdin);
13、设置输出宽度和前缀
// 前面有25-3=22个空格
cout << setw(25) << 256 << endl;
// 前面有7个9
cout << setfill('9') << setw(10) << 256;
14、全排列
#include <bits/stdc++.h>
using namespace std;
int a[20] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
bool vis[20];
int n, b[20];
void dfs(int s) {
if (s == n) {
for (int i = 0; i < n; i++)
cout << b[i] << ' ';
cout << endl;
return;
}
for (int i = 0; i < n; i++)
if (!vis[i]) {
vis[i] = true;
b[s] = a[i];
dfs(s + 1);
vis[i] = false;
}
return;
}
int main() {
cin >> n;
dfs(0);
}
15、n个数参与m层全排列
#include <bits/stdc++.h>
using namespace std;
bool vis[20];
int a[20] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20};
int b[20], step;
void dfs(int s,int step) {
if (s == step) {
for (int i = 0; i < step; i++)
printf("%d ", b[i]);
printf("\n");
return;
}
for (int i = 0; i < 20; i++)
if (!vis[i]) {
vis[i] = true;
b[s] = a[i];
dfs(s + 1, step);
vis[i] = false;
}
}
int main() {
scanf("%d", &step);
dfs(0, step);
return 0;
}
16、前n个数的所有组合(不含空集合的所有子集)
#include <bits/stdc++.h>
using namespace std;
bool vis[20];
int a[20] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20};
int step;
void dfs(int s) {
if (s == step) {
for (int i = 0; i < step; i++)
if (vis[i])
printf("%d ", a[i]);
printf("\n");
return;
}
else {
vis[s] = true;
dfs(s + 1);
vis[s] = false;
dfs(s + 1);
}
}
int main() {
scanf("%d", &step);
dfs(0);
return 0;
}
17、最大公约数、最小公倍数
// 最大公约数
cout << __gcd(25, 5) << endl;
// 最小公倍数lcm
cout << 25 * 5 / __gcd(25, 5) << endl;
18、十进制转换为任意进制
itoa函数,格式:itoa(十进制数,字符数组名称,转换的进制数)
将十进制的n转化为m进制,并将结果存储在c数组中
#include <bits/stdc++.h>
using namespace std;
int n;
char c[25];
int main() {
// 输入十进制、输出任意进制
cin >> n;
cout << itoa(n, c, m);
return 0;
}
19、多重背包问题
类似于0/1背包
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
int n, V, cnt, a, b, s, v[MAXN], w[MAXN], f[MAXN];
int main() {
cin >> n >> V;
for (int i = 1; i <= n; i++) {
cin >> a >> b >> s;
int k = 1;
//实现1 2 4 8件原物品,合成为新物品
while (k <= s) {
v[++cnt] = k * a;
w[cnt] = k * b;
s -= k;
k *= 2;
}
if (s) {
v[++cnt] = s * a;
w[cnt] = s * b;
}
}
//01背包
for (int i = 1; i <= cnt; i++)
for (int j = V; j >= v[i]; j--)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[V];
return 0;
}
20、分组背包问题
for (int i = 1; i <= n; i++)
for (int j = m; j >= 0; j++)
//s[i]表示第i组物品的个数
for (int k = 1; k <= s[i]; k++)
//剩余的背包容量j大于第i组的第k个物品的体积
if(j >= v[i][k])
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
21、包子凑数
#include <bits/stdc++.h>
using namespace std;
int N, ans = 0, a[101], dp[10001];
int main() {
cin >> N >> a[0];
int g = a[0];
for (int i = 1; i < N; i++) {
cin >> a[i];
g = __gcd(g, a[i]);
}
if (g != 1) {
cout << "INF";
return 0;
}
fill(dp, dp + 10000, 10000);
dp[0] = 0;
// 背包问题
for (int i = 1; i < 10001; i++)
for (int j = 0; j < N; j++)
if (i - a[j] >= 0)
dp[i] = min(dp[i], dp[i - a[j]] + 1);
for (int i = 1; i < 10001; i++)
if (dp[i] >= 10000)
ans++;
cout << ans;
return 0;
}
22、超级质数
求不了子数字,但能求子字符串
#include <bits/stdc++.h>
using namespace std;
int n, cnt, ans, son_int;
string son_str;
bool is_prime(int target) {
for (int i = 2; i <= sqrt(target); ++i)
if (target % i == 0)
return false;
return true;
}
int main() {
for (int i = 2; i < 1010; i++) {
//字符串化
string str = to_string(i);
cnt = 0;
n = str.length();
// 遍历所有长度
for(int j = 1; j <= n; ++j)
for(int k = 0; k <= n - j; ++k) {
son_str = str.substr(k, j);
son_int = stoi(son_str);
if(!is_prime(son_int))
cnt++;
}
if (cnt == 0)
ans = son_int;
}
cout << ans;
return 0;
}
23、冒泡排序
循环n次
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j++)
if (a[j] < a[j - 1])
swap(a[j], a[j - 1]);
24、最小生成树
prim算法:
首先从某个点出发,找到与之相连的最小权值的边,并将其回贴到图中,然后在此时的最小生成树中,找到某点与(还未加入最小生成树中的某点)形成的最小权值的边,并将其回贴到图中,如此递归形成最后的结果,详情见代码
#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9 + 10;
const int maxn = 5010;
const int maxm = 2e5 + 10;
struct edge{
int v, w, next;
}e[maxm * 2];
//cnt表示有向边的条数
//tot表示已连接的最小生成树的边
//now代表此时准备连接哪个点
//ans表示结果
int cnt, n, m, tot, now = 1, ans;
//head表示某点作为前驱节点时的编号
//dis表示已经加入最小生成树的点到没有加入的点的最小距离
//vis判断某个点是否已经加入最小生成树
int head[maxn], dis[maxn], vis[maxn];
void add(int u, int v, int w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
void prim() {
for(int i = 2; i <= n; ++i)
dis[i] = INF;
//注意重边
//找出所有与1相连的点,记录两点间的最短距离
for(int i = head[1]; i; i = e[i].next)
dis[e[i].v] = min(dis[e[i].v], e[i].w);
//最小生成树边数等于点数 - 1
while(++tot < n) {
int minn = INF;
//标记走过的点
vis[now] = 1;
int t = now;
//枚举每一个没有使用的点
//找出最小值作为新边
for(int i = 1; i <= n; ++i)
if(!vis[i] && minn > dis[i]) {
minn = dis[i];
now = i;
}
//如果最新的结点未被修改,则无法形成最小生成树
//故该图不连通,则需输出orz
if(now == t) {
printf("orz");
return;
}
ans += minn;
//枚举now的所有连边,更新dis数组
for(int i = head[now]; i; i = e[i].next) {
int v = e[i].v;
if(dis[v] > e[i].w && !vis[v])
dis[v] = e[i].w;
}
}
printf("%d", ans);
}
int main() {
scanf("%d %d", &n, &m);
int u, v, w;
for(int i = 1; i <= m; ++i) {
scanf("%d %d %d", &u, &v, &w);
//该图为无向图
//故需要当作两条边加入结构体中
add(u, v, w);
add(v, u, w);
}
prim();
return 0;
}
Kruskal算法:
将所有的边按权值的大小从小到大进行排序,选取权值最小的边,回贴到图中并判断是否形成了环,若形成了环,则丢弃该边,继续下一条边的回贴,若没有形成环,则递归调用,继续下条边的判断。此时的判断有没有形成环,可以用有没有公共祖先进行判断,若存在公共祖先,则会形成环,否则不会,详情见代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
const int maxv = 2e5 + 10;
int n, m, ans, f[maxv];
//因为该图为无向图,故存储时需要将数组长度扩大一倍
struct edge{
int u, v, w;
}g[maxn];
bool cmp(edge a,edge b) {
return a.w < b.w;
}
int find(int a) {
if (f[a] == a)
return a;
return f[a] = find(f[a]);
}
void construct() {
int cnt = 0;
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= m; i++) {
int fx = find(g[i].u);
int fy = find(g[i].v);
//该步骤是为了判断是否形成了环
if (fy != fx) {
f[fx] = fy;
ans += g[i].w;
cnt++;
}
if (cnt == n - 1) {
printf("%d", ans);
break;
}
}
if(cnt != n - 1)
printf("orz");
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++)
scanf("%d %d %d", &g[i].u, &g[i].v, &g[i].w);
sort(g + 1, g + 1 + m, cmp);
construct();
return 0;
}
25、链式前向星
初始化定义结构体
// 定义结构体
struct E {
int to, w, next;
}Edge[maxm];
int tot, Head[maxm];
插入节点,从头部插入
// 插入节点
void AddEdge(int u,int v,int w) {
Edge[tot].to = v;
Edge[tot].w = w;
Edge[tot].next = Head[u];
Head[u] = tot++;
}
遍历
for(int i = Head[u]; ~i; i = Edge[i].next) {
int v = Edge[i].to;
int w = Edge[i].w;
}
26、取模符号
取模运算的符号取决于被除数
printf("%d\n", 7 % (-3));//1
printf("%d\n", (-7) % 3);//-1
27、二叉树的遍历(前序、中序、后序)
#include <bits/stdc++.h>
using namespace std;
struct Node {
int data;
Node *left, *right;
}root[1050];
int n, l, r;
//前序遍历
void PreOrderTree(Node* root) {
if (root == NULL)
return;
printf("%d ", root->data);
PreOrderTree(root->left);
PreOrderTree(root->right);
}
//中序遍历
void MidOrderTree(Node* root) {
if (root == NULL)
return;
MidOrderTree(root->left);
printf("%d ", root->data);
MidOrderTree(root->right);
}
//后序遍历
void PostOrderTree(Node* root) {
if (root == NULL)
return;
PostOrderTree(root->left);
PostOrderTree(root->right);
printf("%d ", root->data);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d %d", &l, &r);
root[i].data = i;
//若l不为零,则将该结点作为左孩子
if(l)
root[i].left = &root[l];
//若r不为零,则将该结点作为右孩子
if(r)
root[i].right = &root[r];
}
//从根节点开始遍历
PreOrderTree(&root[1]);
printf("\n");
MidOrderTree(&root[1]);
printf("\n");
PostOrderTree(&root[1]);
return 0;
}
28、快速幂
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll base, power;
ll fastPower(ll base,ll power) {
ll result = 1;
while (power > 0) {
if (power % 2 == 1)
result *= base;
power /= 2;
base *= base;
}
return result;
}
int main() {
// 求base的power次方
cin >> base >> power;
cout << fastPower(base, power);
return 0;
}
29、线性筛素数
#include<bits/stdc++.h>
using namespace std;
const int N = 1e8 + 10;
int n, q, k, cnt, isPrime[N], Prime[N];
void GetPrime() {
// 初始化,令每个数均为素数
memset(isPrime, 1, sizeof(isPrime));
isPrime[1] = 0;
for(int i = 2; i <= n; ++i) {
// 若这个数没有被筛掉,则记为素数
if(isPrime[i])
Prime[++cnt] = i;
// 不管i是不是素数,需要筛掉i和所有已知的素数的积,但是每个积只需要筛一次
// 由这个积除以最小质因数的商来筛掉
for(int j = 1; j <= cnt && i * Prime[j] <= n; ++j) {
// 若有某个数可以整除i和已经筛选出来的素数,则标记
isPrime[i * Prime[j]] = 0;
// 如果当前的i有Prime[j]这个质因数,假设i = k * Prime[j]
// 后续就没有必要使用i和更大的质因数继续筛选下去了
// eg.数i * Prime[j + 1] = k * Prime[j] * Prime[j + 1]
// 此时应该使用k * Prime[j + 1]进行筛选(除以最小质因数的商)
// 减少时间复杂度
if(i % Prime[j] == 0)
break;
}
}
}
int main() {
scanf("%d %d", &n, &q);
GetPrime();
while(q--) {
scanf("%d", &k);
printf("%d\n", Prime[k]);
}
return 0;
}
30、汉诺塔
#include <bits/stdc++.h>
using namespace std;
void move(char A, char C, int n) {
cout << "把第" << n << "个圆盘从" << A << "--->" << C << endl;
}
void HanoiTower(char A, char B, char C, int n) {
if (n == 1)
move(A, C, n);
else {
//将n-1个圆盘从A柱借助于C柱移动到B柱上
HanoiTower(A, C, B, n - 1);
//将A柱子最后一个圆盘移动到C柱上
move(A, C, n);
//将n-1个圆盘从B柱借助于A柱移动到C柱上
HanoiTower(B, A, C, n - 1);
}
}
int main() {
int n = 0;
cout << "输入A柱子上的圆盘个数:";
cin >> n;
//将n个圆盘从A柱借助于B柱移动到C柱上
HanoiTower('A', 'B', 'C', n);
return 0;
}
31、vector
#include <bits/stdc++.h>
using namespace std;
vector<int> v;
int main() {
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
// insert(起始地址,插入个数,插入字符)
v.insert(v.begin(), 5, 9);
// 遍历vector
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
cout << *it << endl;
return 0;
}
32、字符串string
#include <bits/stdc++.h>
using namespace std;
int main() {
string s = "abcdefgbc";
// 正序查找
cout << s.find("bc") << endl;
// 倒序查找
cout << s.rfind("bc") << endl;
//insert(插入位置,插入字符)
s.insert(0, "123");
cout << s << endl; // 123abcdefgbc
//erase(擦除位置,擦除长度)
s.erase(1, 3); // 1bcdefgbc
cout << s << endl;
//replace(起始位置,替代长度,替代字符串)
s.replace(1, 3, "xyz"); // 1xyzefgbc
cout << s << endl;
// toupper()函数,字符串转大写
for( int i = 0; i < s.size(); i++ ) // 1XYZEFGBC
s[i] = toupper(s[i]);
cout << s << endl;
// tolower()函数,字符串转小写
for( int i = 0; i < s.size(); i++ ) // 1xyzefgbc
s[i] = tolower(s[i]);
cout << s << endl;
// str.append("***");在字符串str后面添加字符串
// compare()函数:比较字符串 s1.compare(s2)
cout << s.compare("abc") << endl;
//其他查找
vector<int> v;
v.push_back(123456789);
if (find(v.begin(), v.end(), 123456789) != v.end())
cout << "win";
return 0;
}
33、优先队列
#include <bits/stdc++.h>
using namespace std;
//升序队列 小顶堆 great 小到大
priority_queue <int, vector<int>, greater<int> > pri_que1;
//降序队列 大顶堆 less 大到小 默认
priority_queue <int, vector<int>, less<int> > pri_que2;
int main() {
// 插入元素
pri_que1.push(1);
pri_que1.push(5);
pri_que1.push(2);
pri_que1.push(8);
pri_que1.push(3);
// 判断是否为空
while(!pri_que1.empty()) {
// 输出第一个元素
cout << pri_que1.top() << ' ';
// 删掉第一个元素
pri_que1.pop();
}
cout << endl;
pri_que2.push(1);
pri_que2.push(5);
pri_que2.push(2);
pri_que2.push(8);
pri_que2.push(3);
while(!pri_que2.empty()) {
cout << pri_que2.top() << ' ';
pri_que2.pop();
}
return 0;
}
34、尺取法
题目描述:给定一个长度为n的数组a和一个数s,在这个数组中找一个区间,使得这个区间之和等于s。输出区间的起点和终点位置。
思路:
1.初始值i=0、j=0,即开始都指向第一个元素a[0],定义sum是区间[i,j]的和,初始值sum = a[0]。
2.如果sum等于s,输出一个解。继续,把j往后挪一位,并把sum的值加上这个新元素
3.如果sum大于s,让sum减掉元素a[i],并把i往后移动一位。
4.如果sum小于s,把j往后挪一位,并把sum的值加上这个新元素。
#include <bits/stdc++.h>
using namespace std;
int a[9] = {1, 2, 3, 4, 10, 4, 3, 2, 1};
int i = 0, j = 0, sum = a[0], target = 10;
int main() {
while (i <= j && i <= 8 && j <= 8) {
// 等于情况下输出后把j挪后一位,注意不要挪i,否则可能会导致程序提前终止
if (sum == target) {
cout << i << ' ' << j << endl;
j++;
sum = sum + a[j];
continue;
}
// 大于情况下减去i对应值
if (sum > target) {
sum = sum - a[i];
i++;
continue;
}
// 小于情况下加上j对应值
if (sum < target) {
j++;
sum = sum + a[j];
continue;
}
}
return 0;
}
35、KMP算法
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int Next[maxn], n1, n2;
string str, mo;
void GetNext() {
int i = 0, j = -1;
while (i < n2) {
if (j == -1 || mo[i] == mo[j]) {
i++;
j++;
Next[i] = j;
}
else
j = Next[j];
}
}
void kmp() {
int cnt = 0;
int i = 0, j = 0;
while (i < n1) {
if (j == -1 || str[i] == mo[j]) {
i++;
j++;
}
else
j = Next[j];
if (j == n2) {
// 计算次数
cnt++;
// 返回位置
cout << i - n2 + 1 << endl;
j = Next[j];
}
}
cout << cnt;
}
int main() {
cin >> str >> mo;
n1 = str.length();
n2 = mo.length();
Next[0] = -1;
GetNext();
kmp();
return 0;
}
36、LIS算法(最长上升子序列)+ 二分
#include <bits/stdc++.h>
using namespace std;
// a数组为数据,dp[i]表示长度为i + 1的LIS结尾元素的最小值
int a[99999], dp[99999];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
// 初始化为无限大
dp[i] = INT_MAX;
}
// 记录dp当前最后一位的下标
int pos = 0;
dp[0] = a[0];
for (int i = 1; i < n; i++) {
// 若a[i]大于dp数组最大值,则直接添加
if (a[i] > dp[pos])
dp[++pos] = a[i];
// 否则找到dp中第一个大于等于a[i]的位置,用a[i]替换之
else
// 二分查找,在从小到大的排序数组中
// lower_bound( begin, end, num):从数组的[begin,end)二分查找第一个大于或等于num的数字
// 找到返回该数字的地址,不存在则返回end
// 通过返回的地址减去起始地址begin, 得到找到数字在数组中的下标
// upper_bound( begin, end, num):从数组的[begin,end)二分查找第一个大于num的数字
// 找到返回该数字的地址,不存在则返回end
// 通过返回的地址减去起始地址begin, 得到找到数字在数组中的下标
dp[lower_bound(dp, dp + pos + 1, a[i]) - dp] = a[i];
}
cout << pos + 1;
return 0;
}
37、LCS算法(最长公共子序列)
#include<bits/stdc++.h>
using namespace std;
int DP[1000][1000], n, m;
//长度
int LCS_length(string a, string b) {
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++) {
if (a[i - 1] == b[j - 1])
DP[i][j] = DP[i - 1][j - 1] + 1;
else if (DP[i - 1][j] >= DP[i][j - 1])
DP[i][j] = DP[i - 1][j];
else
DP[i][j] = DP[i][j - 1];
}
return DP[m][n];
}
//具体公共字符串
void LCS(string a, string b, int i, int j) {
//设置边界
if (i == 0 || j == 0)
return;
if (a[i - 1] == b[j - 1]) {
LCS(a, b, i - 1, j - 1);
cout << a[i - 1];
}
else if (DP[i - 1][j] > DP[i][j - 1])
LCS(a, b, i - 1, j);
else
LCS(a, b, i, j - 1);
}
int main() {
string a, b;
cin >> a >> b;
m = a.size();
n = b.size();
cout << LCS_length(a, b) << endl;
LCS(a, b, m, n);
return 0;
}
38、树状数组
单点修改、区间求和
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m, a, flag, p, q, tree[N];
//非负整数n在二进制表示下最低位1及其后面的0构成的数值
//eg.lowbit(12) = lowbit((1100)2) = (100)2 = 4
//将1100按位取反后加一得到0100,会发现除了最低位的一和后面的零,其余位上与原数均相反
//故两者按位与后正好得到最低位1及其后面的0构成的数值
//又取反加一为补码,故lowbit为k & -k
int lowbit(int k) {
return k & -k;
}
//tree[x]保存以x为根的子树中叶节点值的和
//将x转化为二进制后,发现每一层的末尾的零的个数都相同
//且tree[x]覆盖的长度即为lowbit(x)的值
//tree[x]的父节点为tree[x + lowbit(x)]
void add(int x, int k) {
while(x <= n) {
tree[x] += k;
x += lowbit(x);
}
}
//可知,若求前7项的和,则该值为tree[7] + tree[6] + tree[4]
//故,通过循环可以求出结果
int sum(int x) {
int ans = 0;
while(x != 0) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a);
add(i, a);
}
for(int i = 1; i <= m; ++i) {
scanf("%d %d %d", &flag, &p, &q);
if(flag == 1)
add(p, q);
else
printf("%d\n", sum(q) - sum(p - 1));
}
return 0;
}
区间修改,单点查询:
重点在差分数组上
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m, now, last, flag, p, q, num, tree[N];
int lowbit(int k) {
return k & -k;
}
void add(int x, int k) {
while(x <= n) {
tree[x] += k;
x += lowbit(x);
}
}
int query(int x) {
int ans = 0;
while(x != 0) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
int main() {
scanf("%d %d", &n, &m);
//计算差分数组,将相差的值放入数组中
//eg.原本的数组应为a[] = {1, 6, 8, 5, 10}
//则差分数组应为b[] = {1, 5, 2, -3, 5}
for (int i = 1; i <= n; ++i) {
scanf("%d", &now);
add(i, now - last);
last = now;
}
for (int i = 1; i <= m; ++i) {
scanf("%d", &flag);
//若要修改区间[p, q]的值
//例如上述举的例子,若要将区间[2, 4]均加上3
//则原数组变为a[] = {1, 9, 11, 8, 10}
//差分数组变为b[] = {1, 8, 2, -3, 2}
//即对差分数组来说只需修改下标为p的值,和下标为q + 1的值
if (flag == 1) {
scanf("%d %d %d", &p, &q, &num);
add(p, num);
add(q + 1, -num);
}
//若查询某个点的值
//前p个差分数组的值相加即为该点的值
//与单点修改、区间查询中的求前缀和类似
else {
scanf("%d", &p);
printf("%d\n", query(p));
}
}
return 0;
}
39、景区导游(LCA、图论)
样例输入:
6 4
1 2 1
1 3 1
3 4 2
3 5 2
4 6 3
2 6 5 1
样例输出:
4
本题主要考察LCA
若要求u->v的路径长度,等于求u->root+v->root - 2*(root->LCA(u,v))
即分别从u和v走到根结点,再减去2倍的根结点到(u,v)的最近公共祖先的路径长度
同理,若原本的遍历顺序是a->b->c,总路径长度为sum,若要跳过b
则新的路径长度为sum-dist(a,b)-dist(b,c)+dist(a,c),可画图验证
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 100;
int n, k;
int deep[N];//深度
int dp[N][21];//dp[i][j]表示从i结点开始跳2^j步可到达的结点
vector<int> edge[N];//边
vector<int> weight[N];//权值
ll path[N];//原始的游览路线
ll dist[N];//dist[i]存储i到根结点的距离
//LCA的前置算法(模板)
void dfs(int u,int father) {
deep[u] = deep[father] + 1;
dp[u][0] = father;
for (int i = 1; i <= 20; i++)
dp[u][i] = dp[dp[u][i - 1]][i - 1];
for (int i = 0; i < edge[u].size(); i++) {
int v = edge[u][i], w = weight[u][i];
if (v == father)
continue;
dist[v] = dist[u] + w;
dfs(v, u);
}
}
//求x和y的最近公共祖先(模板)
int LCA(int x, int y) {
if (deep[x] < deep[y])
swap(x, y);
for (int i = 20; i >= 0; i--)
if (deep[dp[x][i]] >= deep[y])
x = dp[x][i];
if (x == y)
return x;
for (int i = 20; i >= 0; i--)
if (dp[x][i] != dp[y][i]) {
x = dp[x][i];
y = dp[y][i];
}
return dp[x][0];
}
//求x和y的距离
ll get_dist(int x,int y) {
if (x == 0 || y == 0)
return 0;
return dist[x] + dist[y] - 2 * dist[LCA(x, y)];
}
int main() {
scanf("%d%d", &n, &k);
//插入n-1条无向边
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
edge[u].push_back(v);
edge[v].push_back(u);
weight[u].push_back(w);
weight[v].push_back(w);
}
//跑一遍dfs为LCA做准备
dfs(1, 0);
// sum存储原始游览路线的总路径长度
ll sum = 0;
for (int i = 1; i <= k; i++) {
scanf("%d", &path[i]);
//依次累加
sum += get_dist(path[i], path[i - 1]);
}
//除去第i个景点
for (int i = 1; i <= k; i++) {
ll dist1 = get_dist(path[i], path[i - 1]);
ll dist2 = get_dist(path[i], path[i + 1]);
ll dist3 = get_dist(path[i - 1], path[i + 1]);
printf("%lld ", sum - dist1 - dist2 + dist3);
}
printf("\n");
return 0;
}
40、砍树(LCA、图论)
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 50;
vector<int> e[maxn];
ll fa[maxn], dep[maxn], son[maxn], siz[maxn], top[maxn];
ll diff[maxn];
void dfs1(ll u, ll f) {
fa[u] = f;
dep[u] = dep[f] + 1;
siz[u] = 1;
for (auto x: e[u]) {
ll v = x;
if (v == f)
continue;
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
son[u] = v;
}
}
void dfs2(ll u, ll t) {
top[u] = t;
if (!son[u])
return;
dfs2(son[u], t);
for (auto x: e[u]) {
ll v = x;
if (v != son[u] && v != fa[u]) dfs2(v, v);
}
}
ll lca(ll x, ll y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]])
swap(x, y);
x = fa[top[x]];
}
return dep[x] > dep[y] ? y : x;
}
// 给路径打上标记,若标记数大于m,则说明删除后可以满足要求
void dfs(int x, int fx) {
for (auto y: e[x]) {
if (y == fx)
continue;
dfs(y, x);
diff[x] += diff[y];
}
}
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
dfs1(1, 1);
dfs2(1, 1);
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
diff[u]++;
diff[v]++;
diff[lca(u, v)] -= 2;
}
dfs(1, 0);
ll ans = -1;
for (int i = 0; i <= n; i++)
if (diff[i] >= m)
ans = i - 1;
cout << ans;
return 0;
}
41、依次读取数据,事先并不知道有多少数据
int a[N], cnt;
while (scanf("%d", &a[cnt]) != EOF)
cnt++;
42、STL全排列函数
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
string s = "bca";
//字符串内部排序,得到最小的排列"abc"
sort(s.begin(), s.end());
//s.end()指向最后一个字符的下一个位置
// abc acb bac bca cab cba
// 全排列函数,用来求后一个排列
do {
cout << s << " ";
} while (next_permutation(s.begin(), s.end()));
printf("\n");
// 从大到小进行排序
sort(s.begin(), s.end(), greater<>());
// cba cab bca bac acb abc
// 全排列函数,用来求前一个排列
do {
cout << s << " ";
} while (prev_permutation(s.begin(), s.end()));
return 0;
}
43、求数组最大/最小值
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n = 8, a[] = {10, 5, 95, 65, 445, 6852, 57, 864};
cout << *max_element(a, a + n) << endl; // 6852
cout << *min_element(a, a + n) << endl; // 5
return 0;
}