01背包问题
背包状态方程----动态规划
二维dp
使用 f[i][j] = max(f[i-1][j] ,f[i-1][j - w[i]] + v[i]);
伪代码:
cpp
int dp[100][100];
void test6() {
int n; //装备数量
int m; //背包容量
int v[105], w[105]; //前面空间,后面价值
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
if (j >= v[i]) {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
}
else {
dp[i][j] = dp[i - 1][j];
}
}
}
}
其中,dp中的横坐标i代表的是第i个装备,纵坐标 j 为背包容量,由于这里dp一直记录的是最大的,所以可以优化为一维dp
同理的一道题:
使用一维dp,注意的是:进行更新 f[ ]时,要从后面开始更新,这样可以使用的f[j - v[i]] 全是上次草药的值,如果从小往大进行更新,会导致后面更新,装两次一样的草药。导致错误。
cpp
void test3() {
int timeAll, num;
scanf("%d%d", &timeAll, &num);
int v[105],w[105];
int f[1005];
for (int i = 1; i <= num; i++) {
scanf("%d %d", &v[i], &w[i]);
}
for (int i = 0; i <= timeAll; i++) {
f[i] = 0;
}
for (int i = 1; i <= num; i++) {
for (int j = timeAll; j >= 0; j--) {
if (j >= v[i]) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
else {
f[j] = f[j];
}
}
}
printf("%d", f[timeAll]);
}
桶排序+暴力处理
这里就不断处理最大值,最大湿度衣服进行烘干,然后暴力执行。
cpp
//堆排序
int f[500005];
void test5() {
int n, a, b;
scanf("%d%d%d", &n, &a, &b);
int max = 0, sum = 0,time = 0;
for (int i = 1; i <= n; i++) {
int k;
scanf("%d", &k);
f[k]++;
if (k > max) {
max = k;
}
}
while (1) {
if (max <= sum) {
break;
}
f[max]--;
if (max > b) {
f[max - b] += 1;
}
while(f[max] == 0) {
max--;
}
sum += a;
time += 1;
}
printf("%d", time);
}
二分答案:
题解 P2678 【跳石头】 - 洛谷专栏 (luogu.com.cn)https://www.luogu.com.cn/article/08mxthsv
运用二分答案,进行枚举判断答案是否正确
比如:
使用check函数检查答案是否正确,运用二分答案:两个要素
单调性,有界性。答案可以枚举出来
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int H[1000005];
int N, M;
bool check2(int l) {
int count = 0;
for (int i = 1; i <= N; i++) {
if (H[i] > l) {
count += H[i] - l;
if (count >= M) {
return true;
}
}
}
return false;
}
int max(int a, int b) {
return a > b ? a : b;
}
void test8() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; i++) {
scanf("%d", &H[i]);
}
sort(H + 1, H + 1 + N);
int left = 1, right = H[N], ans = 0;
while (left <= right) {
int mid = (left + right) / 2;
if (check2(mid)) {
ans = max(ans, mid);
left = mid + 1;
}
else {
right = mid - 1;
}
}
cout << ans;
}
int main() {
test8();
}
还有这题:P1824 进击的奶牛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
同理:使用二分答案,解决
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
int V[200005];
int N, C, H;
using namespace std;
int check(int k) {
int last = V[1], count = 0;
for (int i = 2; i <= N; i++) {
if (V[i] - last < k) {
count++;
if (H < count)return 1;
}
else {
last = V[i];
}
}
return 0;
}
int max(int a, int b) {
return a > b ? a : b;
}
void swap(int* a, int* b) {
int t = *a;
*a = *b;
*b = t;
}
void test7() {
scanf("%d %d", &N, &C);
for (int i = 1; i <= N; i++) {
scanf("%d", &V[i]);
}
H = N - C;
sort(V+1,V+N+1);
int left = 1, right = V[N], ans = 0;
while (left <= right) {
int mid = (left + right) / 2;
if (check(mid) == 0) {
ans = max(mid, ans);
left = mid + 1;
}
else {
right = mid - 1;
}
}
printf("%d", ans);
}
int main() {
test7();
}
并查集
比如:
P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
找祖先,就一直找到最初的组件,判断是否相等,如果相等,就说明这两个是在同一个集合中。
cpp
int find(int x) {
while (x != f[x]) {
x = f[x];
}
return x;
}
//路径压缩,循环实现
int find(int x){
while (x != f[x]){
f[x] = f[f[x]];
x = f[x]
}
return x;
}
//路径压缩,递归实现
int find(int x){
if(x == f[x])
return x;
return f[x] = find[f[x]];
}
路径压缩后会可以使查询祖先时,快不少。
并查集还有一个重要的出来查询之外,还有合并了
这里可以直接把一个元素的祖先赋值给一个元素的祖先
cpp
void merge(int a,int b){
int a1 = find(a);
int a2 = find(b);
f[a1] = a2;
}
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int N, M;
int f[10005];
void init() {
for (int i = 1; i <= N; i++) {
f[i] = i;
}
}
int find(int x) {
while (x != f[x]) {
f[x] = f[f[x]];
x = f[x];
}
return x;
}
void test8() {
cin >> N >> M;
init();
for (int i = 1; i <= M; i++) {
int z, x, y;
cin >> z >> x >> y;
if (z == 1) {
f[find(x)] = find(y);
}
else if (z == 2) {
if (find(x) == find(y)) {
cout << "Y\n";
}
else {
cout << "N\n";
}
}
}
}
int main() {
test8();
}
最小生成树
算法:prim,kruskal算法
主要学习了kruskal算法
运用到了并查集。P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
顺便学习了c++中sort函数,自定义排序方式,
图中的边权值,进行排序。
kruskal:先找出最小没有连接的边,然后判断如果连接后有没有回路,如果有回路,就舍弃这条边,没有就把这条边加入,然后再进行判断下一条边。
P1195 口袋的天空 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
类似的,这个是需要把n个点构造成k棵最小生成树,所以只要加入的边等于n-k就行了
附上代码
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int N, M, K;
int f[1005];
struct edge {
int v, u, w;
}edges[10005];
int find(int x) {
while (f[x] != x) {
f[x] = f[f[x]];
x = f[x];
}
return x;
}
bool cmp(edge a, edge b) {
return a.w < b.w;
}
void kruskal() {
int ans = 0, cnt = 0;
sort(edges + 1, edges + M + 1, cmp);
for (int i = 1; i <= M; i++) {
int a1 = find(edges[i].u);
int a2 = find(edges[i].v);
if (a1 == a2) {
continue;
}
ans += edges[i].w;
f[a1] = a2; //连接两个顶点
cnt++;
if (cnt == N - K) {
cout << ans;
return;
}
}
cout << "No Answer";
}
void test() {
cin >> N >> M >> K;
for (int i = 1; i <= N; i++) {
f[i] = i;
}
for (int i = 1; i <= M; i++) {
cin >> edges[i].u >> edges[i].v >> edges[i].w;
}
kruskal();
}
int main() {
test();
}
最短路径
dijkstra算法+链式前向星存边,
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <algorithm>
#include<cstring>
#define inf 2147483647
using namespace std;
long long dis[10005];
bool vis[10005];
int head[10005];
int n, m, s,cnt = 0;
//在这里head数组储存i点的一条边的编号,edge中的next表示的是起点相同的边的编号,
//可以从加边那里深刻理解
//链式前向星存边
struct edge {
int next;
int to;
int val;
}edges[500005];
void dijkstra() {
dis[s] = 0;
int cur = s;
while (!vis[cur]) {
vis[cur] = true;
for (int i = head[cur]; i != 0; i = edges[i].next) {
if (!vis[edges[i].to] && dis[edges[i].to] > dis[cur] + edges[i].val) {
dis[edges[i].to] = dis[cur] + edges[i].val;
}
}
int min = inf;
for (int i = 1; i <= n; i++) {
if (!vis[i] && min > dis[i]) {
min = dis[i];
cur = i;
}
}
}
for (int i = 1; i <= n; i++) {
cout << dis[i] <<" ";
}
}
//加边
void add(int a,int b,int c) {
edges[++cnt].next = head[a];
edges[cnt].to = b;
edges[cnt].val = c;
head[a] = cnt; //记录编号
}
void test() {
cin >> n >> m >> s;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
for (int i = 1; i <= n; i++) {
dis[i] = inf;
}
dijkstra();
}
int main() {
test();
}
训练总结:昨天晚上加今天一天完成了这所有的训练,确实收获不少。
靠思维,暴力解决:A,,K,L
动态规划还是不太熟练, B
二分答案,能够理解。 C,D,E,F
并查集:G,H
最小生成树:H,I
最短路径:J
思路有个大概,但是完全自己想出来,还是有点难度