关闭同步流:
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
注意数据范围:数据范围较大时干脆所有变量类型都定义成longlong等。
stl:
sort函数
时间复杂度为nlog(n);
sort(数组指针,从指针开始多少个数,greater<int>()/cmp):
三个参数,第三个参数不加默认为从小到大排,加了greater<int>为从大到小排,也可以自定义函数cmp
bool cmp(int x,int y){
return x>y
}
如果返回的是false,则会把传入的第一个参数放到第二个参数后面,如果两个数相等,必须返回false,不然会报错。
unique:
该函数的作用是“去除”容器或者数组中相邻元素的重复出现的元素,注意
(1) 这里的去除并非真正意义的erase,而是将重复的元素放到容器的末尾,返回值是去重之后的尾地址。
(2) unique针对的是相邻元素,所以对于顺序顺序错乱的数组成员,或者容器成员,需要先进行排序,可以调用std::sort()函数
返回数组中最后一个不重复的数的位置索引
#include <iostream>
#include <algorithm>
int main(void)
{
int a[8] = {2, 2, 2, 4, 4, 6, 7, 8};
int c;
//std::sort(a, a + 8); //对于无序的数组需要先排序
c = (std::unique(a, a + 8) - a );
std::cout<< "c = " << c << std::endl;
//打印去重后的数组成员
for (int i = 0; i < c; i++)
std::cout<< "a = [" << i << "] = " << a[i] << std::endl;
return 0;
}
动态数组vector:
需要引入头文件<vector>
声明:
vector<type> vec;
vector<type> vec(n,1); //第一个参数为数组长度(后续仍能继续添加),第二个参数为初始化的值,不加则默认0。
方法:
push_back(); //末尾添加数据
pop_back(); //末尾删除数据,要是想删中间的需要手动移动整个数组
.size(); //获取vector的长度
.clear(); //清空vector,但是不会释放内存,要释放内存可以用 vector <int>().swap(v)
二维数组:
vector<vector<int> > vec; //注意有个空格,不能省略
按上面的定义,刚开始vec里面都是空的,我们需要先往里面push空的一维vector数组
vec.push_back(vector<int>());
初始化方法:vector<vector<int> > vec(n,vector(m,0));
也可以建立vector类型的数组:
vector<int> x[100];
集合set:
由不重复的元素组成。
声明:
set<type> s;
方法:
.insert() //往集合里插入数据,如果数据已经存在,就不会插入
.erase(); //删除元素,找不到元素则不进行任何操作
.count(); //查找元素是否存在,存在返回1,不存在返回0
.clear(); //会清空集合并释放内存
.size(); //获取元素个数
遍历:
要遍历集合需要用到迭代器
for(set<string>::iterator it = s.begin(); it != s.end(); it++){
cout << *it << endl;
}
//自动会从小到大遍历
集合经常会搭配结构体使用,此时遍历会无法判断结构体的大小,需要重载<符号
struct node {
int x,y;
bool operator<(const Node &rhs) const{
if (x == rhs.x){
return y < rhs.y;
}
else return x < rhs.x;
}
}
pair:
头文件utility,由一对元素组成,可以看作有两个属性first和second的结构体,重载了<运算符,先比较first大小,如果等于再比较second
声明:
pair<type1,type2> p;
方法:
make_pair(v1,v2); //返回一个由v1,v2初始化的pair,类型可以从v1,v2反推出来
映射集合map:
存储key->value键值对,一个key只能对应一个value,一个value可以对应多个key
声明:
map<type1,type2> m;
方法:
.insert(); //插入的类型为pair类型,如果key存在不会执行
map['key']; //获取key对应的value,如果没有对key做过映射,会自动为其创建映射,初始值为其类型的默认值
map['key']=value2; //更改对应的值
.count('key'); //检测key值是否存在,存在返回1,否则返回0
.size(); //返回映射的个数
.claer(); //清空map
遍历:
与set类似,也需要使用迭代器
此外:
map也可以套set,套map
map<int , set<string> > s; //注意空格!
cmath头文件:
fabs(); //取绝对值
round(); //四舍五入取整
sqart(); //开方函数
最大公约数计算:
代码练习:
二维单调队列:
在一维单调队列的基础上,先进行行的单调队列,再进行列的单调队列
我简化了列题,改为求长度为n的正方形区域中的最大值
代码:
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int n, m, k, w;
int g[1005][1005];
int head, tail; //窗口的头尾指针
int d[1005]; //单调队列
int maxx[1005][1005];
int ans[1005][1005];
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> g[i][j];
}
}
//对每一行做单调队列
memset(d, 0, sizeof(d));
for (int i = 1; i <= n; i++) {
head = tail = 0;
for (int j = 1; j <= m; j++) {
if (j != 1 && d[head] < j - k + 1) head++;
while (tail != head && g[i][j] > g[i][d[tail - 1]]) tail--;
d[tail++] = j;
maxx[i][j] = g[i][d[head]];
cout << g[i][d[head]] << " ";
}
cout << endl;
}
//对每一列做单调队列
memset(d, 0, sizeof(d));
for (int i = k; i <= m; i++) {
head = tail = 0;
for (int j = 1; j <= n; j++) {
if (j != 1 && d[head] < n - k + 1) head++;
while (head != tail && maxx[j][i] > maxx[d[tail - 1]][i]) tail--;
d[tail++] = j;
ans[j][i] = maxx[d[head]][i];
cout << maxx[d[head]][i] << " ";
}
cout << endl;
}
int ans2 = 0;
for (int i = k; i <= n; i++) {
for (int j = k; j <= m; j++) {
ans2 = max(ans2, ans[i][j]);
}
}
cout << ans2;
}
前缀异或和:
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
long long a[100005];
long long s[100005];
long long ans;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
s[0] = 0;
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] ^ a[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
ans += s[i - 1] ^ s[j];
}
}
cout << ans;
}
蓝桥杯真题:
拆位+有效位思想
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
long long a[100005];
long long ans;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 0; i < 20; i++) {
//s为前缀和,n1为奇数,n0为偶数
long long s = 0;
long long n1 = 0;
long long n0 = 1;
for (int j = 1; j <= n; j++) {
long long t = a[j] >> i & 1;
s += t;
if (s % 2) {
//此处n0必须设为longlong,longlong和int相乘结果是int导致越界
ans += (1 << i) * n0;
n1++;
}
else {
ans += (1 << i) * n1;
n0++;
}
}
}
cout << ans;
}
并查集:
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
int fa[1005];
int n;
//初始化
void intt() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
//找父亲节点
int find(int i) {
if (i == fa[i]) return i;
else {
//路径压缩
fa[i] = find(fa[i]);
return fa[i];
}
}
//合并
void unionn(int i, int j) {
int fai = find(i);
int faj = find(j);
fa[fai] = faj;
}
int main() {
cin >> n;
}
DFS:
深搜剪枝:
找路:
剪枝思路,last数组用于记录一个方块附近可到达的方块数量,如果该方块附近可到达的方块的附近只有一个可到达的方块(且这个方块不为目标地点),那么我们就必须走这一个方块,因为需要一进一出,如果此时不走,将来走进去了就出不来了。同时这样的方块只能有一块,大于一块就可以直接return。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
using namespace std;
bool vis[10][10];
int last[10][10];
int dx[4] = { -1,0,1,0 };
int dy[4] = { 0,1,0,-1 };
int n,m,ans;
bool in(int x, int y) {
return x <= n && y <= n && x >= 1 && y >= 1;
}
void dfs(int x, int y, int step) {
if (x == n && y == 1 && step != m) {
return;
}
if (!in(x, y) || vis[x][y]) {
return;
}
if (x == n && y == 1 && step == m) {
ans++;
return;
}
int dir = -1;
int cnt = 0;
vis[x][y] = true;
for (int i = 0; i < 4; i++) {
int ex = x + dx[i];
int ey = y + dy[i];
if (in(ex, ey) && !vis[ex][ey]) {
last[ex][ey]--;
if (last[ex][ey] < 2) {
if (!(ex == n && ey == 1)) {
dir = i;
cnt++;
}
}
}
}
if (cnt >= 2) {
for (int i = 0; i < 4; i++) {
int ex = x + dx[i];
int ey = y + dy[i];
if (in(ex, ey) && !vis[ex][ey]) {
last[ex][ey]++;
}
}
vis[x][y] = false;
return;
}
if (dir != -1) {
dfs(x + dx[dir], y + dy[dir], step + 1);
}
else {
for (int i = 0; i < 4; i++) {
int ex = x + dx[i];
int ey = y + dy[i];
if (in(ex, ey) && !vis[ex][ey]) {
dfs(x + dx[i], y + dy[i], step + 1);
}
}
}
vis[x][y] = false;
for (int i = 0; i < 4; i++) {
int ex = x + dx[i];
int ey = y + dy[i];
if (in(ex, ey) && !vis[ex][ey]) {
last[ex][ey]++;
}
}
}
int main() {
cin >> n;
m = n * n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int m = 0; m < 4; m++) {
int ex = i + dx[m];
int ey = j + dy[m];
if (in(ex, ey)) {
last[i][j]++;
}
}
}
}
dfs(1, 1, 1);
cout << ans;
}
蓝桥杯真题: 卖瓜
//方案1 无敌剪枝(全过)
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int ans = 100;
int n;
double m;
double s[35];
double pre[35];
void dfs(int num, double sum, int cnt) {
//找到方案
if (sum == m) {
if (cnt < ans) { ans = cnt; }
return;
}
//切的次数已经大于最优解
if (cnt >= ans) { return; }
//总重超过目标重量
if (sum > m) return;
//n个瓜都遍历完了
if (num > n) return;
//总重不可能到目标重量
if (sum + pre[num] < m) return;
dfs(num + 1, sum + s[num] / 2, cnt + 1);
dfs(num + 1, sum + s[num], cnt);
dfs(num + 1, sum, cnt);
return;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
}
//贪心算法,让大的瓜放在前面
sort(s + 1, s + n + 1,greater<double>());
//记录后缀和,用于剪枝
for (int i = n; i >= 1; i--) {
pre[i] = s[i] + pre[i + 1];
}
dfs(1,0, 0);
if (ans == 100) { cout << -1; }
else cout << ans;
return 0;
}
//方案2 折半枚举+hash表查找
//这里不会写hash表直接拿unordered_map只能拿78分,因此主要还是学一下折半枚举的思想
#include<bits/stdc++.h>
using namespace std;
unordered_map <double, int> PII;
int ans = 100;
int n;
double m;
double s[35];
double pre[35];
void dfs1(int num, double sum, int cnt) {
if (sum > m) return;
if (num == n / 2 + 1) {
PII[sum] = cnt;
return;
}
dfs1(num + 1, sum + s[num], cnt);
dfs1(num + 1, sum, cnt);
dfs1(num + 1, sum + s[num] / 2, cnt + 1);
return;
}
void dfs2(int num, double sum, int cnt) {
if (cnt > ans) return;
if (sum > m) return;
if (sum <= m && PII.count(m - sum)) {
ans = min(ans, PII[m - sum] + cnt);
}
if (num > n) return;
dfs2(num + 1, sum + s[num], cnt);
dfs2(num + 1, sum, cnt);
dfs2(num + 1, sum + s[num] / 2, cnt + 1);
return;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
}
贪心算法,让大的瓜放在前面
sort(s + 1, s + n + 1, greater<double>());
dfs1(1, 0, 0);
dfs2(n / 2 + 1, 0, 0);
if (ans == 100) { cout << -1; }
else cout << ans;
return 0;
}
BFS:
1.平面三维魔方复原
思路就是用一个结构体记录魔方的状态,用一个队列记录魔方的状态和已进行的步数,将每次12种可能都入栈,采用BFS。在node中重载了==号。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
using namespace std;
struct node {
int a[5][5];
bool operator < (const node& x) const {
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
a[i][j] < x.a[i][j];
return 1;
}
}
return 0;
}
bool operator == (const node& x) const {
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (a[i][j] != x.a[i][j])
return 0;
}
}
return 1;
}
};
node first;
node des;
node line(node t,int i, int flag) {
//左移
if (flag == 1) {
int x = t.a[i][1];
t.a[i][1] = t.a[i][2];
t.a[i][2] = t.a[i][3];
t.a[i][3] = x;
} //右移
else if (flag == 0) {
int x = t.a[i][3];
t.a[i][3] = t.a[i][2];
t.a[i][2] = t.a[i][1];
t.a[i][1] = x;
}
return t;
}
node col(node t, int i, int flag) {
//上移
if (flag == 1) {
int x = t.a[1][i];
t.a[1][i] = t.a[2][i];
t.a[2][i] = t.a[3][i];
t.a[3][i] = x;
} //下移
else if (flag == 0) {
int x = t.a[3][i];
t.a[3][i] = t.a[2][i];
t.a[2][i] = t.a[1][i];
t.a[1][i] = x;
}
return t;
}
void bfs(node t) {
queue<pair<node , int>> q;
q.push({ t,0 });
while (!q.empty()) {
auto [tt , step] = q.front();
q.pop();
if (tt == des) {
cout << step;
exit(0);
}
for (int i = 1; i <= 3; i++) {
q.push({ line(tt,i,1),step + 1 });
q.push({ line(tt,i,0),step + 1 });
q.push({ col(tt,i,1),step + 1 });
q.push({ col(tt,i,0),step + 1 });
}
}
}
int main() {
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
des.a[i][j] = (i-1) * 3 + j;
}
}
for (int i = 1; i <= 3; i++) {
int n;
cin >> n;
for (int j = 3; j >= 1; j--) {
first.a[i][j] = n % 10;
n /= 10;
}
}
bfs(first);
}
2.分糖果(数据结构):
思路就是利用vector数组来建立邻接表,然后bfs搜索即可,直接用二维数组会过大导致编译失败
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
vector<int> a[100010];
int maxstep;
bool vis[100010];
void bfs(int c) {
queue<pair<int, int>> q;
q.push({ c , 0 });
while (!q.empty()) {
auto [num,step] = q.front();
q.pop();
if (step > maxstep) {
maxstep = step;
}
for (int i = 0; i < a[num].size(); i++) {
if (!vis[a[num][i]]) {
q.push({ a[num][i], step + 1 });
vis[a[num][i]] = 1;
}
}
}
}
int main() {
int n, p, c, m,x,y;
cin >> n >> p >> c >> m;
for (int i = 0; i < p; i++) {
cin >> x >> y;
a[x].push_back(y);
a[y].push_back(x);
}
vis[c] = true;
bfs(c);
cout << maxstep + m;
}
相似度(思路):
思路:每个选手的字符与其本身的相似度最大,即为m(特征总数),以这些字符为起点,全部放入堆栈,然后依次修改这些字符的每一位数字,每次修改得到的字符最大相似度会-1,如果得到的字符已经被访问过了就不管,没访问过就把最大相似度-1并入队列,这样越进行到后面最大相似度越小,直到最后获得最小的。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
queue<int> q; //存放每组数,用二进制的值来代表每组数据
int f[100000]; //存放每组数的最大相似度
int n, m;
int mincnt = 100000;
vector<int> mi(100000,-1);
void ext(int x, int cnt) {
if (f[x] == -1) {
f[x] = cnt;
q.push(x);
if (cnt < mincnt) {
mincnt = cnt;
for (int i = m-1; i >= 0; i--) {
mi[i] = x % 2;
x /= 2;
}
}
}
}
int main() {
cin >> n >> m;
for (int i = 0; i < 1 << m; i++) {
f[i] = -1;
}
for (int i = 0; i < n; i++) {
int sum = 0;
string s;
cin >> s;
for (int j = 0; j < m; j++) {
sum = sum * 2 + s[j]-'0';
}
f[sum] = m;
q.push(sum);
}
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = 0; i < m; i++) {
//更改每一位的数字
ext(x ^ (1 << i), f[x] - 1);
}
}
for (int i = 0; i < m; i++) {
cout << mi[i];
}
}
动态规划:
一维消消乐:
思路:从左往右进行选择,每个珠子有两种状态,与前面一个相消和不消,如果不消,分数就为前面一个两种状态的较大值,如果相消,则为前一个不消状态的分数加上这两个珠子相消的分数。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
int x[10005];
int stau[10005][2];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> x[i];
}
stau[0][0] = stau[0][1] = 0;
for (int i = 1; i < n; i++) {
stau[i][0] = max(stau[i - 1][0], stau[i - 1][1]);
stau[i][1] = stau[i - 1][0] + x[i] * x[i - 1];
}
cout << max(stau[n - 1][0], stau[n - 1][0]);
}
数组分组:
ans[i]代表对前i个数字分组的最大权值,对于i+1个数字的分组,则考虑ans[n]+n到i为一组的权值,n从1到i。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
int pre[1005][1005];
int a[1005];
int ans[1005];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
//预处理
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
if (i == j) pre[i][j] = a[i];
else {
pre[i][j] = pre[i][j - 1] * a[j] % 1000;
}
}
}
ans[0] = 0;
ans[1] = a[1];
for (int i = 2; i <= n; i++) {
for (int j = 1; j < i; j++) {
ans[i] = max(ans[i], ans[j] + pre[j + 1][i]);
}
}
cout << ans[n];
}
常见模型:
二维数组可以看作一维数组,比如3x3的二维数组,可以看成6种一维数组,第一、二、三行,1+2行,1+3行,2+3行,1+2+3行。
1.最大子段和
思路:考虑从1到i这i个数的最大子段和(子段和中必须包括第i个数),记为sum。在考虑1到i+1的最大子段和时只需要考虑sum加上i+1的值是否为正,为正则更新sum的值,为负则令sum=0。这样就舍掉了所有前缀和为负数的情况。用一个ans变量与sum比较来更新最大值即可。
上述的思想是建立在存在一个正数的情况,所以如果全为负数需要单独考虑,此时只需要取最大的
负数即可。
2.最长上升子序列
3.最长公共子序列
4.字符串转变
数组dp[i][j]代表字符串s1前i位与s2前j位对齐需要操作数。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
int a[1005];
int b[1005];
int dp[1005][1005];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++){
cin >> b[i];
}
for (int i = 1; i <= n; i++) {
dp[i][0] = i;
}
for (int i = 1; i <= m; i++) {
dp[0][i] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i] == b[j]) {
dp[i][j] = dp[i - 1][j - 1];
}
else dp[i][j] = min(min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 1);
}
}
cout << dp[n][m];
}
5.最长公共上升子序列
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
int a[1005];
int b[1005];
int dp[1005][1005]; //dp[i][j]代表a组i个和b组j个的最大公共子序列,这个序列的结尾是b组的第j个数
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
for (int i = 1; i <= n; i++) {
int maxx = 0;
for (int j = 1; j <= m; j++) {
//这里dp[i - 1][j]表示以b[j]为结尾的最大公共子序列,因此多加一个a[i]不会影响
dp[i][j] = dp[i - 1][j];
//maxx记录a组i个和b组所有的最大公共子序列,因此a[i] > b[j]才更新
if (a[i] > b[j] && maxx < dp[i-1][j]) {
maxx = dp[i - 1][j];
}
else if(a[i] == b[j]) {
dp[i][j] = maxx + 1;
}
}
}
int maxx = 0;
for (int i = 1; i <= m; i++) {
maxx = max(maxx, dp[n][i]);
}
cout << maxx;
}
背包问题:
01背包:
空间优化:
多重背包:
优化:将n个相同物品的n用二进制拆封,例如7个相同物品可以拆成1+2+4这样三个物品.
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
int w[1005];
int c[1005];
int n[1005];
int nc[1005];
int nw[1005];
int dp[1005][1005];
int main() {
int m, v;
cin >> m >> v;
for (int i = 1; i <= m; i++) {
cin >> w[i] >> c[i] >> n[i];
}
int k;
int cnt = 0;
for (int i = 1; i <= m; i++) {
for (k = 1; n[i] - (1 << k) + 1 > 0; k++) {
nc[cnt] = (1 << (k - 1)) * c[i];
nw[cnt] = (1 << (k - 1)) * w[i];
cnt++;
}
k--;
nc[cnt] = (n[i] - (1 << k) + 1) * c[i];
nw[cnt] = (n[i] - (1 << k) + 1) * w[i];
}
for (int i = 1; i <= cnt; i++) {
for (int j = 1; j <= v; j++) {
if (j > nc[i])
dp[i][j] = max(dp[i][j - nc[i]] + nw[i], dp[i - 1][j]);
else dp[i][j] = dp[i - 1][j];
}
}
}
完全背包:
物品无限,例题存钱罐:
钱的重量为e-f,满足这个情况的最小金额
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
const int inf = 0x3f3f3f3f;
//dp[i]代表重量为i时的最小金额
int dp[1000];
int main() {
//memset将整数数组全部赋值为非0整数值会出错,推荐for循环设置,这里是看老师这么做的,原理不明
memset(dp, inf, sizeof dp);
dp[0] = 0;
int a, b, n;
cin >> a >> b;
int v = b - a;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a >> b;
for (int j = b; j <= v; j++) {
dp[j] = min(dp[j], dp[j - b] + a);
}
}
if (dp[v] == inf) {
cout << "imposible";
}
else cout << dp[v];
}
双塔(好题) :
思路写在注释里了。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
int h[10005];
int dp[1005][1005];//dp[i][j]表示考虑到第i号水晶,两座塔差距为j时较低的塔的最大高度
int main() {
int n;
int sum = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> h[i];
}
memset(dp, -1, sizeof dp);
dp[0][0] = 0;
//每一种状态可以有四种产生方式,高塔的更高,低塔变高但还是小于高塔,低塔变高大于高塔,不选择这块水晶状态不变
//因此可以用动态规划遍历四种情况选出最优解
for (int i = 1; i <= n; i++) {
sum += h[i];
for (int j = 0; j <= sum; j++) {
//不选择水晶
if (dp[i - 1][j] > dp[i][j]) {
dp[i][j] = dp[i - 1][j];
}
//高的更高
if (j >= h[i] && dp[i - 1][j - h[i]] >= 0) {
dp[i][j] = max(dp[i][j], dp[i - 1][j - h[i]]);
}
//低塔变高但还是小于高塔
if (dp[i - 1][j + h[i]] >= 0) {
dp[i][j] = max(dp[i][j], dp[i - 1][j + h[i]] + h[i]);
}
//低塔变高大于高塔
if (j <= h[i] && dp[i - 1][h[i] - j] >= 0) {
dp[i][j] = max(dp[i][j], dp[i - 1][h[i] - j] + h[i] - j);
}
}
}
if (dp[n][0] > 0) {
cout << dp[n][0];
}
else cout << "impossible";
}
图和树:
最短路径:
有权图:
Dijkstra:
注意这个算法需要在每一轮中找出到未标记的节点中路径最短的节点,而spfa算法不用
//借助临界表
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
struct edge {
//v为边指向的节点,w为边权
int v, w, next; //next记录的是与该边共起点的一条边的序号,与head数组配合可以遍历从一个节点发出的所有边
edge() {};
edge(int vv, int ww ,int nextt) {
v = vv;
w = ww;
next = nextt;
}
}e[100000];
int head[100000]; //head[u]从u起点发出的边中最后一条,搭配next属性可以从这条边开始遍历从u发出的所有边
int num; //边的序号
int n, m;
bool vis[100000];
int dis[100000];
void init() {
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis)); //设置很大的数
num = 0;
}
void insert2(int u, int v, int w) {
e[num] = edge(v, w, head[u]);
head[u] = num++;
}
void insert(int u, int v, int w) {
insert2(u, v, w); //无向图相当于每个节点之间两条边
insert2(v, u, w);
}
void Dijkstra(int u) {
dis[u] = 0;
for (int i = 1; i <= n; i++) {
int mind = 10000000, minj = -1;
for (int j = 1; j <= n; j++) {
//找到到某个点的距离最小且这个点还没被访问过的点
if (!vis[j] && dis[j] < mind) {
mind = dis[j];
minj = j;
}
}
if (minj == -1) {
return;
}
vis[minj] = true;
for (int j = head[minj]; j != -1; j = e[j].next) {
int w = e[j].w;
int v = e[j].v;
if(!vis[v] && w + dis[minj] < dis[v]){
dis[v] = w + dis[minj];
}
}
}
}
int main() {
init();
cin >> n >> m;
int u, v, w;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
insert(u, v, w);
}
Dijkstra(1);
for (int i = 1; i <= n; i++) {
cout << dis[i] << endl;
}
}
//借助临界矩阵
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
int dis[10000];
int pre[10000];
int map[10000][10000];
bool vis[10000];
int n,m;
void Dis(int u) {
dis[u] = 0;
for (int i = 0; i < n; i++) {
int mind = 1000000;
int minj = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && dis[j] < mind) {
mind = dis[j];
minj = j;
}
}
if (minj == -1) {
return;
}
vis[minj] = true;
for (int k = 1; k <= n; k++) {
if (map[minj][k] != 0x3f3f3f3f) {
if (dis[k] > dis[minj] + map[minj][k]) {
dis[k] = dis[minj] + map[minj][k];
pre[k] = minj;
}
}
}
}
}
int main() {
cin >> n >> m;
int u, v, w;
memset(dis, 0x3f, sizeof(dis));
memset(pre, -1, sizeof(pre));
memset(vis, 0, sizeof(vis));
memset(map, 0x3f, sizeof(map));
for (int i = 1; i <= n; i++) {
map[i][i] = 0;
}
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
map[u][v] = map[v][u] = w;
}
Dis(1);
cout << dis[n];
}
堆优化Dijkstra:
关于优先队列priority_queue函数的使用:
头文件#include<queue>
定义:priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式。
greater<Type>是小根堆,less<Type>是大根堆
//升序队列,小顶堆
2 priority_queue <int,vector<int>,greater<int> > q;
3 //降序队列,大顶堆
4 priority_queue <int,vector<int>,less<int> >q;
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
typedef pair<int, int> p;
priority_queue<p, vector<p>, greater<p>> heap;
int num = 0;
int n, m, u, v, w;
int head[100000];
int dis[10000];
bool vis[100000];
struct edge {
int w, v, next;
edge() {};
edge(int vv, int ww, int nextt) {
w = ww;
v = vv;
next = nextt;
}
}e[100000];
void init() {
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis));
}
void insert2(int u, int v, int w) {
e[num] = edge(v, w, head[u]);
head[u] = num++;
}
void insert(int u, int v, int w) {
insert2(u, v, w);
insert2(v, u, w);
}
void dist(int x) {
dis[x] = 0;
heap.push({ dis[x], x});
while (!heap.empty()) {
p temp = heap.top();
int mind = temp.first;
int minj = temp.second;
heap.pop();
if (vis[minj]) continue;
vis[minj] = true;
for (int j = head[minj]; j != -1; j = e[j].next) {
int v = e[j].v;
int w = e[j].w;
if (!vis[v] && dis[v] > dis[minj] + w) {
dis[v] = dis[minj] + w;
heap.push({ dis[v],v });
}
}
}
}
int main() {
init();
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
insert(u, v, w);
}
dist(1);
for (int i = 1; i <= n; i++) {
cout << dis[i] << endl;
}
}
spfa:
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
struct edge {
//v为边指向的节点,w为边权
int v, w, next; //next记录的是与该边共起点的一条边的序号,与head数组配合可以遍历从一个节点发出的所有边
edge() {};
edge(int vv, int ww ,int nextt) {
v = vv;
w = ww;
next = nextt;
}
}e[100000];
int head[100000]; //head[u]从u起点发出的边中的一条,搭配next属性可以从这条边开始遍历从u发出的所有边
int num; //边的序号
int n, m;
bool vis[100000];
int dis[100000];
void init() {
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis)); //设置很大的数
num = 0;
}
void insert2(int u, int v, int w) {
e[num] = edge(v, w, head[u]);
head[u] = num++;
}
void insert(int u, int v, int w) {
insert2(u, v, w); //无向图相当于每个节点之间两条边
insert2(v, u, w);
}
void spfa(int u) {
dis[u] = 0;
vis[u] = true;
queue<int> ed; //队列存放边的序号
ed.push(u);
while (!ed.empty()) {
int minj = ed.front();
ed.pop();
vis[minj] = false;
for (int j = head[minj]; j != -1; j = e[j].next) {
int w = e[j].w;
int v = e[j].v;
if (w + dis[minj] < dis[v]) {
dis[v] = w + dis[minj];
//有更新才会入栈
if (!vis[v]) {
ed.push(v);
vis[v] = true;
}
}
}
}
}
int main() {
init();
cin >> n >> m;
int u, v, w;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
insert(u, v, w);
}
spfa(1);
for (int i = 1; i <= n; i++) {
cout << dis[i] << endl;
}
}
//判断是否存在负环
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
struct edge {
//v为边指向的节点,w为边权
int v, w, next; //next记录的是与该边共起点的一条边的序号,与head数组配合可以遍历从一个节点发出的所有边
edge() {};
edge(int vv, int ww, int nextt) {
v = vv;
w = ww;
next = nextt;
}
}e[100000];
int head[100000]; //head[u]从u起点发出的边中的一条,搭配next属性可以从这条边开始遍历从u发出的所有边
int num; //边的序号
int n, m;
bool vis[100000];
int dis[100000];
int cnt[1000000]; //记录每个点入栈的次数,用于判断负环
void init() {
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis)); //设置很大的数
memset(cnt, 0, sizeof(cnt));
num = 0;
}
void insert2(int u, int v, int w) {
e[num] = edge(v, w, head[u]);
head[u] = num++;
}
void insert(int u, int v, int w) {
insert2(u, v, w); //无向图相当于每个节点之间两条边
insert2(v, u, w);
}
int spfa(int u) {
dis[u] = 0;
cnt[u] = 1;
vis[u] = true;
queue<int> ed; //队列存放边的序号
ed.push(u);
while (!ed.empty()) {
int minj = ed.front();
ed.pop();
vis[minj] = false;
for (int j = head[minj]; j != -1; j = e[j].next) {
int w = e[j].w;
int v = e[j].v;
if (w + dis[minj] < dis[v]) {
dis[v] = w + dis[minj];
//有更新才会入栈
if (!vis[v]) {
ed.push(v);
cnt[v]++;
vis[v] = true;
if (cnt[v] > n) {
return 0;
}
}
}
}
}
return 1;
}
int main() {
init();
cin >> n >> m;
int u, v, w;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
insert(u, v, w);
}
if (spfa(1)) {
cout << "不存在负环" << endl;
}
else cout << "存在负环" << endl;
}
Floyd:
时间复杂度:O(V的三次方)
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
int g[105][105]; //邻接矩阵
//g[k][i][j]表示一种状态,即只经过1到k中的点的从i点到j点的最短路径,可以由两种状态转变而成,一种是不经过k点,另一种就是经过k点
//g[k][i][j] = min (g[k-1][i][j], g[k-1][i][k] + g[k-1][k][j])
//因为g[k-1][i][k] + g[k-1][k][j] 完全等价于 g[k][i][k] + g[k][k][j]
//所以可以等价变换成g[k][i][j] = min (g[k-1][i][j], g[k][i][k] + g[k][k][j])
//因此我们可以从小到大枚举k,舍弃k的这一维空间,即g[i][j] = min(g[i][j], g[i][k] + g[k][j])
//后面的g[i][j]是在前一轮循环中产生的最小路径,即g[k-1][i][j],随后新一轮循环k++,此时得到的g[i][j]即为g[k][i][j]
void floyd(int n) {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}
}
}
}
int main() {
int n, m, u, v, w;
cin >> n >> m;
memset(g, 0x3f, sizeof(g));
for (int i = 1; i <= 100; i++) {
g[i][i] = 0;
}
while (m--) {
cin >> u >> v >> w;
g[u][v] = g[v][u] = w;
}
floyd(n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << i << " " << j << " " << g[i][j] << endl;
}
}
}
kruskal最大/小生成树:
Kruskal算法也叫避圈法,基于贪心策略,它通过不断选择图中权重最小的边来构建最小生成树。算法的主要思想是从所有边中按照权重从小到大进行选择,如果选择的边不会构成环(即不会形成闭合回路),就将该边加入最小成树中。为了避免环的形成,算法使用了并查集(Disjoint Set)数据结构来判断顶点是否连接。
二、Kruskal算法步骤
将所有边按照权重从小到大进行排序。
初始化并查集。
从排好序的边中选择权重最小的边。
如果该边连接的两个顶点不在同一个连通分量中(即不会形成环),则将该边加入最小生成树,并合并这两个顶点的连通分量。
重复步骤3和步骤4,直到最小生成树中包含了V-1条边,其中V为顶点数。
例题:蓝桥杯真题
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
int n, m, q, next;
int fa[1000005];
int head[1000005];
int ans = 0x3f3f3f3f;
int vis[100005];
int len = 0;
int flag = 0;
//edge用来存最开始题目给的边,edge2用来存kruskal筛选后的边
struct edge {
int u, v, w, next;
edge() {};
edge(int uu, int vv, int ww) {
u = uu;
v = vv;
w = ww;
}
bool operator <(const edge& rhs) const {
return rhs.w > w;
}
}e[300005];
struct edge2 {
int v, w, next;
edge2() {};
edge2(int vv,int ww,int nextt) {
v = vv;
next = nextt;
w = ww;
}
}e2[1000000];
//insert和insert2就是迪利斯特拉算法的模板
void insert2(int u, int v, int w) {
e2[len] = edge2(v, w, head[u]);
head[u] = len++;
}
void insert(int u, int v, int w) {
insert2(u,v,w);
insert2( v,u, w);
}
int cmp(edge a, edge b) {
return a.w > b.w;
}
//并查集模板
void init() {
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int find(int x) {
while (fa[x] != x) {
x = fa[x];
}
return x;
}
bool unionn(int a, int b) {
int aa = find(a);
int bb = find(b);
if (aa == bb) {
return false;
}
else fa[aa] = bb;
return true;
}
//最后题目会询问几组点之间的最小距离,dfs用于遍历最大生成树
void dfs(int u, int v, int dis) {
if (u == v) {
cout << dis << endl;
flag = 1;
return;
}
vis[u] = true;
for (int i = head[u]; i != -1; i = e2[i].next) {
if (!vis[e2[i].v]) dfs(e2[i].v, v, min(dis, e2[i].w));
}
vis[u] = false;
}
int main() {
cin >> n >> m >> q;
int u, v, w;
int size = 0;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
e[size++] = edge(u, v, w);
}
//排序,进行kruskal算法
sort(e, e + m, cmp);
init();
for (int i = 0; i < m; i++) {
if (unionn(e[i].u, e[i].v)) {
insert(e[i].u, e[i].v, e[i].w);
}
}
//遍历目录树得到答案
for (int i = 0; i < q; i++) {
cin >> u >> v;
dfs(u, v, 0x3f3f3f3f);
if(flag == 0) cout << -1 << endl;
flag = 0;
}
}
次短路径:
求出最短路径经过的每个点,每次删除其中的一个点求最短路径,这样求出的所有最短路径中最短的就是次短路径
LCA:
圣诞树:
分析:
所有边的价值等于每个点到根节点的所有边乘上这些边的边权和之和,因此我们只需要从根节点做一次单源最短路径,令所有点到根节点的权值同时最小即可。
地图:
因为每个点都可以经过任意次,需要考虑正环的情况,直接将正环中的点的权值设置为无穷大即可。
成仙之路:
分析:
先不考虑等级问题,先建立起模型,可以将每个物品作为节点(成仙也是一个物品),物品到物品之间需要加的价格为边权,然后自己设置一个0点,0点到每个物品的边的权值就等于这个物品的价格,然后从0点到成仙做最短路。
然后考虑价格问题,如果成仙的价格是L,那么能够交易的范围就在L-M到L+M,但这个范围是2M,我们需要枚举最低的等级,从L-M到L,因为M<20,所以最多枚举20次即可。
网络稳定性:
第一反应是Floyd,但是看了数据的大小感觉只能过30%
//最后过了两组,错了一组,其他超时
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
vector<vector<int>> v;
int main() {
int n,m, x;
cin >> n >> m >> x;
int u, vv, w;
for (int i = 0; i <= n + 1; i++) {
vector<int> v1(n + 2, 0);
v.push_back(v1);
}
for (int i = 1; i <= m; i++) {
cin >> u >> vv >> w;
v[u][vv]=max(v[u][vv],w); //选稳定性最大的一条路
v[vv][u]=max(v[vv][u],w);
}
for (int i = 1; i <= n; i++) {
v[i][i] = 0x3f3f3f3f;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
v[i][j] = max(min(v[i][k], v[k][j]), v[i][j]);
}
}
}
for (int i = 1; i <= x; i++) {
cin >> u >> vv;
if (v[u][vv] == 0) cout << -1 << endl;
else cout << v[u][vv] << endl;
}
}
后来发现可能dfs更快点。
对每个点都做一次dfs,时间复杂度是O(V*E),边数E最大为V的平方,也就是说这个 算法最大时间复杂度为O(V的三次方),和Floyd的固定复杂度一样。
特别是这题不需要枚举所有的点。
小技巧:
1.将9x9分成3x3的九个格子并分别编号
2.奇偶性剪枝
用一个二维数组,前一个是二进制数,后一个是空格的位置来表示状态,
常用函数:
memset:
memset(dp,0,sizeof(dp)); //dp为数组指针,需要头文件cstring。
memset是逐字节赋值,并不是将数组每一个元素的值赋值,参数可以选择0,-1,0x3f
0x3f很大,且两倍的0x3f不会超出int范围。
strcmp()
#include<iostream>
#include<string.h> //or #include<cstring>
using namespace std;
int main()
{
char *a = "11112";
char *b = "11117";
cout<<strcmp(a,b)<<endl;
return 0;
//a==b -> 返回 0;
//a>b -> 返回大于0的数;
//a<b -> 返回小于0的数;
}
reverse
头文件:algorithm
reverse(st, ed):反转一个数组、向量、字符串
islower、isupper
头文件:cctype
islower©:返回值bool,判断一个字符是否是小写或大写字母
tolower、toupper
tolower©:返回一个字符,如果c是大写字母就转化成小写字母
acsii码
大写字母转换为小写字母:c + 32 或 c - ‘A’ + ‘a’
小写字母转换为大写字母:c - 32 或 c - ‘a’ + ‘A’
字符转化成数字:‘6’ - ‘0’ = 6
find函数
#include <bits/stdc++.h>
using namespace std;
int main()
{
int a[10]={2,6,8,1,3,7,5,1,0,11};
cout<<find(a+0,a+5,8)<<endl;//打印类似0x地址
cout<<find(a+0,a+5,8)-a<<endl;//打印数组【】内的序号
return 0;
}