This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
### 第9题
动画演示构造哈夫曼树 理解哈夫曼编码
https://www.bilibili.com/video/BV1qfHzepETR
https://space.bilibili.com/97391932/lists/547856?type=season
原始
| $A$ | $B$ | $C$ | $D$ | $E$ | $F$ | $G$ |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 0.40 | 0.30 | 0.15 | 0.05 | 0.04 | 0.03 | 0.03 |
排序 【居然贴心到不用排序~】
| $A$ | $B$ | $C$ | $D$ | $E$ | $F$ | $G$ |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 0.40 | 0.30 | 0.15 | 0.05 | 0.04 | 0.03 | 0.03 |
开始构建哈夫曼树过程
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202501241537979.png)
### 第10题
https://www.bilibili.com/video/BV1CLCKYuEdg
### 第11题
https://zhuanlan.zhihu.com/p/106828968
### 判断题第2题
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202501241548453.png)

View File

@@ -0,0 +1,41 @@
#include <bits/stdc++.h>
using namespace std;
/*
这行代码报错的原因是:
__data 是 ManyData 类的私有成员(private),在类外部无法直接访问。在 C++ 中,以双下划线 __ 开头的成员默认是私有的,而且这个变量确实被声明在类的 private 部分。
要访问类的私有成员,您需要通过类的公有方法(public methods)来进行操作。
要修复这个错误,您应该使用类提供的公有方法 at() 来访问数据。把报错的那行代码改成:
*/
class ManyData {
int *__data;
int head, tail, capacity;
public:
ManyData(int cap) {
capacity = cap;
__data = new int[capacity];
head = tail = 0;
}
void push(int val) { __data[tail++] = val; }
int pop() { return __data[--tail]; }
int size() { return tail - head; }
int at(int index) {
if (index >= 0 && index < tail - head) {
return __data[head + index];
}
throw out_of_range("Index out of bounds");
}
};
int main() {
auto myData = new ManyData(100);
myData->push(1);
myData->push(2);
myData->push(3);
myData->push(100);
cout << myData->size() << endl;
cout << myData->pop() << endl;
// cout << myData.__data[0] << endl;
cout << myData->at(0) << endl;
return 0;
}

BIN
GESP/Level6/202309.pdf Normal file

Binary file not shown.

View File

@@ -0,0 +1,39 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int v[N], w[N];
int n, L;
int f[N][N * 5]; // 需要扩大第二维
int main() {
cin >> n >> L;
int sum_v = 0;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
sum_v += v[i]; // 累计总花费上限
}
// 动态规划过程
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= sum_v; j++) { // 修正循环范围
f[i][j] = f[i - 1][j];
if (j >= v[i])
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
// 寻找最小花费
int ans = -1;
for (int j = 0; j <= sum_v; j++) { // 遍历所有可能花费
if (f[n][j] >= L) {
ans = j;
break;
}
}
if (ans == -1)
cout << "no solution" << endl;
else
cout << ans << endl;
return 0;
}

View File

@@ -0,0 +1,49 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
/*
5 100
100 2000
2 50
4 40
5 30
3 20
答案:9
*/
int v[N], w[N];
int n, L;
int ans = INT_MAX;
// 走到第u个物品,已经用了c这个多的钱,使用了m这个大的容量
void dfs(int u, int c, int m) {
// 剪枝优化
if (c >= ans)
return;
if (m >= L) {
if (c < ans)
ans = c;
return;
}
// 递归出口
if (u == n + 1)
return;
dfs(u + 1, c + v[u], m + w[u]); // 选择当前物品
dfs(u + 1, c, m); // 放弃当前物品
}
int main() {
cin >> n >> L;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i]; // 售价和容量
dfs(1, 0, 0);
if (ans < INT_MAX)
cout << ans << endl;
else
cout << "no solution" << endl;
return 0;
}

View File

@@ -0,0 +1,47 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int v[N], w[N];
int n, L;
int ans = INT_MAX;
int memo[N][N]; // memo[u][m] 表示处理到第u个物品时,容量m的最小花费
void dfs(int u, int c, int m) {
// 剪枝:当前花费已超过已知最优解
if (c >= ans)
return;
// 统一处理容量达标的情况
m = min(m, L); // 状态压缩
if (m >= L) {
ans = min(ans, c); // 记录可行的解
return;
}
// 记忆化剪枝
if (memo[u][m] != -1 && c >= memo[u][m])
return;
memo[u][m] = c;
// 递归终止条件
if (u > n)
return;
// 选当前物品
dfs(u + 1, c + v[u], m + w[u]);
// 不选当前物品
dfs(u + 1, c, m);
}
int main() {
cin >> n >> L;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
memset(memo, -1, sizeof memo);
dfs(1, 0, 0);
cout << (ans < INT_MAX ? to_string(ans) : "no solution") << endl;
return 0;
}

49
GESP/Level6/20230902.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e5 + 10;
int n;
int q[N];
int t[N];
LL ans;
/*
6
0 1 2 3 4 5
答案:15
*/
// 归并排序,附带求顺序对的数量
void merge_sort(int q[], int l, int r) {
if (l >= r)
return;
int mid = (l + r) >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int i = l;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= r)
if (q[i] <= q[j]) {
t[k++] = q[i++];
// 顺序对,原理:当q[i]<=q[j]时,q[i]与q[j]之后的所有数都构成顺序对
ans += r - j + 1;
} else
t[k++] = q[j++];
while (i <= mid)
t[k++] = q[i++];
while (j <= r)
t[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++)
q[i] = t[j];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> q[i];
// 根据题目提示,使用归并排序,并在归并排序的过程中计算顺序对的数量
merge_sort(q, 1, n);
printf("%lld", ans);
return 0;
}

BIN
GESP/Level6/202312.pdf Normal file

Binary file not shown.

View File

@@ -0,0 +1,60 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int INF = 0x3f3f3f3f;
int n, m; // n个关卡,每个关卡都有一样的m个通道
int a[N]; // 第i个通道可以让你前进a[i]关,,比如:现在第x关,选择i通道,将到达第x+a[i]关
int b[N]; // 当你离开第s关时,将获得b[s]分的奖励
int f[N]; // 动态规划数组,f[i]表示离开第i关时可以获得的最大分数
int ans = -INF; // 通关时最多能够获得的分数
/*
6 2
2 3
1 0 30 100 30 30
答案:131
6 2
2 3
1 0 30 100 30 -1
答案:101
*/
int main() {
cin >> n >> m; // 输入n和m
// 每一个关卡都有m个通道,a[i]表示第i个通道可以让你前进a[i]关
for (int i = 1; i <= m; i++)
cin >> a[i];
for (int i = 0; i < n; i++) // 关卡是从下标0开始的
cin >> b[i]; // 通关i关时可以获得的奖励分数
// 动态规划
// 状态表示:f[i]表示通关第i关时可以获得的最大分数
// 状态转移:f[i] = max(f[i],f[i-a[j]]+b[i-a[j]])
// 初始化:f[0] = 0
// 答案:f[n]
// 因为得分可能是负数,所以初始化的时候,需要将f[i]初始化为-0x3f
memset(f, -0x3f, sizeof f);
f[0] = 0;
for (int i = 1; i < n; i++) { // 遍历每个阶段
// 考虑当前阶段的所有决策,也就是到达这个阶段,可以有哪些决策
// 决策:选择第j个通道,那么到达这个阶段,可以获得的最大分数就是f[i-a[j]]+b[i-a[j]]
// 所以,f[i] = max(f[i],f[i-a[j]]+b[i-a[j]])
for (int j = 1; j <= m; j++) {
if (i - a[j] >= 0)
f[i] = max(f[i], f[i - a[j]] + b[i - a[j]]);
}
}
for (int i = 0; i < n; i++)
for (int j = 1; j <= m; j++)
if (i + a[j] >= n)
ans = max(ans, f[i] + b[i]);
cout << ans << endl;
return 0;
}

View File

@@ -0,0 +1,59 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int INF = 0x3f3f3f3f;
int n, m; // n个关卡,每个关卡都有一样的m个通道
int a[N]; // 第i个通道可以让你前进a[i]关,,比如:现在第x关,选择i通道,将到达第x+a[i]关
int b[N]; // 当你离开第s关时,将获得b[s]分的奖励
int f[N]; // 动态规划数组,f[i]表示离开第i关时可以获得的最大分数
int ans = -INF; // 通关时最多能够获得的分数
/*
6 2
2 3
1 0 30 100 30 30
答案:131
6 2
2 3
1 0 30 100 30 -1
答案:101
*/
int main() {
cin >> n >> m; // 输入n和m
// 每一个关卡都有m个通道,a[i]表示第i个通道可以让你前进a[i]关
int mx = 0;
for (int i = 1; i <= m; i++) {
cin >> a[i];
mx = max(mx, a[i]);
}
for (int i = 0; i < n; i++) // 关卡是从下标0开始的
cin >> b[i]; // 通关i关时可以获得的奖励分数
// 动态规划
// 状态表示:f[i]表示通关第i关时可以获得的最大分数
// 状态转移:f[i] = max(f[i],f[i-a[j]]+b[i-a[j]])
// 初始化:f[0] = 0
// 答案:f[n]
// 因为得分可能是负数,所以初始化的时候,需要将f[i]初始化为-0x3f
memset(f, -0x3f, sizeof f);
f[0] = 0;
for (int i = 1; i < n + mx; i++) { // 遍历每个阶段
// 考虑当前阶段的所有决策,也就是到达这个阶段,可以有哪些决策
// 决策:选择第j个通道,那么到达这个阶段,可以获得的最大分数就是f[i-a[j]]+b[i-a[j]]
// 所以,f[i] = max(f[i],f[i-a[j]]+b[i-a[j]])
for (int j = 1; j <= m; j++) {
if (i - a[j] >= 0)
f[i] = max(f[i], f[i - a[j]] + b[i - a[j]]);
}
}
for (int i = n - 1; i < n + mx; i++)
ans = max(ans, f[i] + b[i]);
cout << ans << endl;
return 0;
}

82
GESP/Level6/20231202.cpp Normal file
View File

@@ -0,0 +1,82 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 310; // 最多300个点
const int M = 310; // 最多299条边,因为它是树,300个点,其实是299条边
// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n;
set<int> a[N]; // 节点i可以管理的节点集合
// 树的深度优先搜索代码
void dfs(int u) { // 以u为根的子树的节点数量
// 遍历一下当前点的所有出边
for (int i = h[u]; i != -1; i = ne[i]) { // i是遍历 u的每条出边
int j = e[i]; // j是u的出边指向的节点
// 递归处理j
dfs(j);
// 针j中的所有孩子记录到u中去,注意:这个合并是要在dfs之后,因为dfs是先递归到最深,然后回溯
for (auto x : a[j])
a[u].insert(x);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("20231202_2.in", "r", stdin);
#endif
// 初始化链式前向星
memset(h, -1, sizeof h);
cin >> n;
// 构建单位的管理结构树
for (int i = 1; i < n; i++) { // 0号是老板
int f;
cin >> f;
add(f, i); // 表示i号员工的领导是f
a[f].insert(i); // f是i的领导
}
// 自己可以管理自己
for (int i = 0; i < n; i++)
a[i].insert(i);
// 递归一遍树
dfs(0);
int q;
cin >> q;
while (q--) {
int m;
cin >> m;
set<int> s; // 需要处理哪些人员集合
for (int i = 1; i <= m; i++) {
int x;
cin >> x;
s.insert(x);
}
// // 从n-1到0,找到第一个包含s的集合
for (int i = n - 1; i >= 0; i--) {
// 检查s中的每个元素是否都在a[i]中
bool flag = true;
for (auto x : s) {
if (a[i].find(x) == a[i].end()) {
flag = false;
break;
}
}
// 如果s中的所有元素都在a[i]中,则i是答案
if (flag) {
cout << i << endl;
break;
}
}
}
return 0;
}

View File

@@ -0,0 +1,6 @@
5
0 0 2 2
3
2 3 4
3 2 3 4
2 1 4

View File

@@ -0,0 +1,8 @@
7
0 1 0 2 1 2
5
2 4 6
2 4 5
3 4 5 6
4 2 4 5 6
2 3 4

BIN
GESP/Level6/202403.pdf Normal file

Binary file not shown.

1
GESP/Level6/20240301.in Normal file
View File

@@ -0,0 +1 @@
114 51 4 1

29
GESP/Level6/20240301.md Normal file
View File

@@ -0,0 +1,29 @@
找一组数字进行模拟
$n=5$
$a=3$
$b=2$
$c=1$
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202501211453606.png)
由此可以整理动态规划的信息如下:
- 状态表示: $f[i]$ 表示从 $i$ 到 $0$ 的方案数
- 状态转移: $f[i] = f[i-a] + f[i-b]$
- 初始状态: $f[n] = 1$
- 目标状态: 因为最后要求是小于$c$的都是可以到达的状态,所以需要从$0$到$c$的$f[i]$进行累加
$$\sum_{i=0}^{c} f[i]$$
- 边界问题处理:
- 如果$i-a$小于$0$,则$f[i-a] = f[0]$
- 如果$i-b$小于$0$,则$f[i-b] = f[0]$
$Q:$ 为什么要处理边界问题,哪来的边界问题呢?
$A:$ 通过上面的数字模拟会发现,$2$可以继续减$3$而到达$-1$,这种转换路径是有效,但结果是$-1$,用数组下标来存储显然是不合适的,所以需要处理边界问题。
我们可以把这样的情况统统的保存汇集到$0$,这样就保证了数组下标是正的。

View File

@@ -0,0 +1,26 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
LL q[N]; // 记忆化
LL n, c, b, a;
const int MOD = 1e9 + 7; // 模 1e9+7
LL dfs(LL x) {
// 走到头了,此时只有一种方案
if (x <= c)
return 1;
// 如果搜索过,直接返回,不再重复计算
if (q[x])
return q[x];
// 如果没有进行搜索就需要计算了
return q[x] = (dfs(x - a) % MOD + dfs(x - b) % MOD) % MOD;
}
int main() {
cin >> n >> a >> b >> c;
cout << dfs(n);
return 0;
}

View File

@@ -0,0 +1,47 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int n, a, b, c;
int f[N];
int ans;
int main() {
#ifndef ONLINE_JUDGE
freopen("20240301.in", "r", stdin);
#endif
/**
例1:
1 1 1 1
答案:1
例2:
114 51 4 1
答案:176
例3:
114514 191 9 810
答案:384178446
*/
// 从大向小推
cin >> n >> a >> b >> c;
f[n] = 1;
for (int i = n; i > c; i--) {
if (i - a >= 0)
f[i - a] = (f[i - a] + f[i]) % MOD;
else
f[0] = (f[i] + f[0]) % MOD;
if (i - b >= 0)
f[i - b] = (f[i - b] + f[i]) % MOD;
else
f[0] = (f[0] + f[i]) % MOD;
}
for (int i = 0; i <= c; i++)
ans = (ans + f[i]) % MOD;
printf("%d\n", ans);
return 0;
}

View File

@@ -0,0 +1,40 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;
int n, a, b, c;
int f[N];
int ans;
int main() {
#ifndef ONLINE_JUDGE
freopen("20240301.in", "r", stdin);
#endif
/**
例1:
1 1 1 1
答案:1
例2:
114 51 4 1
答案:176
例3:
114514 191 9 810
答案:384178446
*/
// 从大向小推
cin >> n >> a >> b >> c;
f[n] = 1;
for (int i = n; i > c; i--) {
f[max(i - a, 0)] = (f[max(i - a, 0)] + f[i]) % MOD;
f[max(i - b, 0)] = (f[max(i - b, 0)] + f[i]) % MOD;
}
for (int i = 0; i <= c; i++)
ans = (ans + f[i]) % MOD;
printf("%d\n", ans);
return 0;
}

33
GESP/Level6/20240302.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 10;
PII a[N];
int ans = INT_MAX;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i].first;
for (int i = 0; i < n; i++)
cin >> a[i].second;
do {
// Q: 为什么res初始化为n?
// A: 因为每个奶牛需要一个牛棚,所以初始化为n
// Q:怎么想到的用全排列?
// A: 因为题目要求的是所有奶牛的组合,所以需要枚举所有组合,而且题目的数据范围极小,N最大才是9,所以用全排列是可行的。
int res = n, k = a[0].second; // 排序第一的牛,它的右手安全距离a[0].second
for (int i = 1; i < n; i++) {
res += max(k, a[i].first); // 当前牛的左手安全距离,加上1才是真正的安全距离
k = a[i].second;
}
ans = min(ans, res); // 每次组合取最小值
} while (next_permutation(a, a + n)); // 枚举所有组合
cout << ans << endl;
return 0;
}

BIN
GESP/Level6/202406.pdf Normal file

Binary file not shown.

37
GESP/Level6/20240601.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[30];
string s;
int f[N]; // f[i]表示前i个字符的最大分值
int main() {
#ifndef ONLINE_JUDGE
freopen("20240601.in", "r", stdin);
#endif
int n;
cin >> n;
// 连续多个abc的分值
for (int i = 1; i <= n; i++)
cin >> a[i];
int m; // 字符串的长度
cin >> m >> s; // 字符串
for (int i = 1; i <= m; i++) { // 枚举每个字符
f[i] = f[i - 1]; // 从前面继承分值
for (int j = 1; j <= n; j++) { // 枚举每个分值,看看有没有机会得到这个分值,就是1个,2个,3个abc...
int start = i - 3 * j;
if (start < 0)
break;
if (s.substr(start, 3) == "abc") // 最后一组是abc,那么有机会获得更高分值
f[i] = max(f[i], f[start + 1] + a[j]);
else
break; // 不是abc,没有继续检查的必要
}
}
cout << f[m] << endl;
return 0;
}

4
GESP/Level6/20240601.in Normal file
View File

@@ -0,0 +1,4 @@
3
3 1 2
13
dabcabcabcabz

7
GESP/Level6/20240602.in Normal file
View File

@@ -0,0 +1,7 @@
6
3 1 1 3 4
100101
3
1
3
2

View File

@@ -0,0 +1,54 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
struct Node {
int color;
int ls = -1, rs = -1; // 左儿子与右儿子
} tr[N];
void dfs(int u) {
if (u == -1)
return;
if (tr[u].color == 0)
tr[u].color = 1;
else
tr[u].color = 0;
dfs(tr[u].ls), dfs(tr[u].rs);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("20240602.in", "r", stdin);
#endif
// 读入
int n;
cin >> n;
for (int i = 1; i < n; i++) {
int x;
cin >> x;
if (tr[x].ls == -1) // 赋值右儿子
tr[x].ls = i + 1;
else // 左儿子
tr[x].rs = i + 1;
}
string s; // 注意:输入的是01串,不是0 1
cin >> s;
for (int i = 1; i <= n; i++)
tr[i].color = s[i - 1] - '0';
int q;
cin >> q;
while (q--) {
int x;
cin >> x;
dfs(x);
}
for (int i = 1; i <= n; i++)
printf("%d", tr[i].color);
return 0;
}

View File

@@ -0,0 +1,56 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
struct Node {
int sum = 0; // 被操作次数
int color;
int ls = -1, rs = -1; // 左儿子与右儿子
} tr[N];
void dfs(int u, int now) {
now += tr[u].sum;
if (u == -1)
return;
if (now & 1)
tr[u].color ^= 1;
dfs(tr[u].ls, now), dfs(tr[u].rs, now);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("20240602.in", "r", stdin);
#endif
// 读入
int n;
cin >> n;
for (int i = 1; i < n; i++) {
int x;
cin >> x;
if (tr[x].ls == -1) // 赋值右儿子
tr[x].ls = i + 1;
else // 左儿子
tr[x].rs = i + 1;
}
string s; // 注意:输入的是01串,不是0 1
cin >> s;
for (int i = 1; i <= n; i++)
tr[i].color = s[i - 1] - '0';
int q;
cin >> q;
while (q--) {
int x;
cin >> x;
tr[x].sum++;
}
dfs(1, 0);
for (int i = 1; i <= n; i++)
printf("%d", tr[i].color);
return 0;
}

View File

@@ -0,0 +1,57 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
int son[N][2]; // 记录每个节点的两个儿子
int fa[N]; // 记录每个节点的父节点是哪个
int col[N]; // 记录每个节点的颜色
int sum[N]; // 记录每个节点被操作的次数
void dfs(int x, int now) {
now += sum[x]; // 递归计算x节点被操作的次数
if (now & 1)
col[x] ^= 1; // 如果操作次数是奇数,则颜色取反
for (int i = 0; i < 2; i++) {
if (son[x][i] != -1)
dfs(son[x][i], now);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("20240602.in", "r", stdin);
#endif
cin >> n; // n个节点
memset(son, -1, sizeof son); // 初始化son数组,-1表示没有儿子
// 这个i从2开始,是因为题目中要求的意义
for (int i = 2; i <= n; i++) {
cin >> fa[i];
for (int j = 0; j < 2; j++) { // 循环两次,因为每个节点有两个儿子
if (son[fa[i]][j] == -1) {
son[fa[i]][j] = i; // 记录父子关系
break;
}
}
}
string s;
cin >> s;
// 初始颜色
for (int i = 1; i <= n; i++)
col[i] = s[i - 1] - '0';
int q;
cin >> q;
while (q--) {
int x;
cin >> x;
sum[x] += 1; // x这个节点被操作了一次
}
// 从根节点开始dfs
dfs(1, 0);
// 输出每个节点的颜色
for (int i = 1; i <= n; i++)
cout << col[i];
return 0;
}

BIN
GESP/Level6/202409.pdf Normal file

Binary file not shown.

17
GESP/Level6/20240901.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
int f[N]; // 状态表示:f[i]表示i个数最少需要多少个平方数
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
f[i] = i; // 初始化,i个数最少需要i个平方数,因为最坏的情况就是每个数都是1
for (int j = 1; j <= sqrt(i); j++) // 枚举完全平方数
f[i] = min(f[i - j * j] + 1, f[i]); // 状态转移方程
}
cout << f[n] << endl;
return 0;
}

40
GESP/Level6/20240902.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int m; // 算法种类数
int n; // 题目数
int k; // 目标掌握程度
int b[N]; // 代表每道题目已经取得的经验值
pair<int, int> a[N]; // 代表每道题目的frist:经验值,second:知识点
int c[N]; // 存储每个知识点选了多少道题
int cnt, ans, mx;
int main() {
cin >> m >> n >> k; // 输入算法种类数,题目数,目标掌握程度
for (int i = 1; i <= n; i++)
cin >> a[i].second; // 输入每道题目的知识点
for (int i = 1; i <= n; i++)
cin >> a[i].first; // 输入学习每道题目可以获取到的经验值
sort(a + 1, a + n + 1); // 按可以获取到的经验值最小到大排序
for (int i = n; i >= 1; i--) { // 倒序枚举,就是经验值最大到小
if (b[a[i].second] < k) { // 第a[i].second,也就是该知识点的已获得的经验值不够k
b[a[i].second] += a[i].first; // 这道题需要做一下,经验值增加
if (b[a[i].second] >= k)
cnt++; // 有多少个知识点达到了k
ans++; // 增加题数(记录使了多少个题目)
c[a[i].second]++; // 统计每个知识点的选择题数
mx = max(mx, c[a[i].second]); // 统计选择最多题的知识点选择了的题数
}
}
// ① 如果最终可以达标的算法数量不够m个,表示无解
// ② 如果选择最多的题数大于题目总数的一半,表示无解
if (cnt < m || n < mx * 2 - 1)
cout << -1 << endl; // 输出-1
else
cout << max(mx * 2 - 1, ans) << endl; // 输出最大值
return 0;
}

82
GESP/Level6/20240902.md Normal file
View File

@@ -0,0 +1,82 @@
## 一、算法选择
**显然**,**对于每个知识点做能得到 <font color="red">更多经验值的题目更优</font>**。
所以,基本上认为是:<font color="red" size="5" bold>【贪心】</font>
## 二、基本思路
既然定位是贪心,那么 **一般的套路就排序**,然后从大到小选择。
用一个 $PII$ 来存储每道题的 **① 经验值**,**② 知识点**,按经验值进行排序,这样系统默认第一个元素为第一关键字,第二个元素为第二关键字,默认是从小到大排序,一会遍历的时候需要倒序进行。
从 $n$ 枚举到$1$,如果第 $i$ 道题的知识点还没有达到 $k$,就要做这道题:
- ① 数组 $b$ 保存每个知识点的 **已经取得的经验值**
- ② 数组 $c$ 保存每个知识点的 **选择题数**
- ③ 变量 $ans$ 保存答案,也就是一共选择了多少道题
- ④ 变量$cnt$ 保存 **达标了的知识点数量**
- ⑤ 变量$mx$ 保存选择题数最高的知识点选择的题数 【下面会讲解为什么要保留这个变量】
> **思考**:
> **问**:为什么会想到记录上面这些变量呢?
> **答**:因为我们不管题目最终想要计算什么,事情发生过程中,客观记录事实最重要,有啥都记录下来总没错。
## 三、结果判定
本题最难想的就是结果的判定,因为它有一个要求:**连续做两道同样知识点的题是不好的**。
我们可以举例子思考可能的所有情况有哪些:
① 一共一个知识点,第一个知识点需要$50$点经验,结果相关联的题目经验值加在一起最多也不到$50$点,此时,输出$-1$。
② 一共两个知识点,第一个知识点需要经验值最高的前$4$道题,第二个知识点需要经验值最高的前$3$道题 ,此时,会发生什么呢?我们会发现,这时候直接输出$4+3=7$就行了。
③ 一共两个知识点,第一个知识点需要经验值最高的前$5$道题,第二个知识点需要经验值最高的前$3$道题 ,此时,会发生什么呢?我们会发现,这时候直接输出$8$是不行的,因为$5$道题,中间最少是$4$个空,现在只有$3$个可以用来间隔的,缺少一个。为什么与 ② 时情况不同呢?就是因为最多的太多了,需要照顾最多的,给它插好空,也就是$mx*2-1$。
④ 继续上面 ③的思考过程 ,那如果总的题目数量也不够$mx*2-1$,此时,输出$-1$。
## 四、代码实现
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int m; // 算法种类数
int n; // 题目数
int k; // 目标掌握程度
int b[N]; // 代表每道题目已经取得的经验值
pair<int, int> a[N]; // 代表每道题目的frist:经验值,second:知识点
int c[N]; // 存储每个知识点选了多少道题
int cnt, ans, mx;
int main() {
cin >> m >> n >> k; // 输入算法种类数,题目数,目标掌握程度
for (int i = 1; i <= n; i++)
cin >> a[i].second; // 输入每道题目的知识点
for (int i = 1; i <= n; i++)
cin >> a[i].first; // 输入学习每道题目可以获取到的经验值
sort(a + 1, a + n + 1); // 按可以获取到的经验值最小到大排序
for (int i = n; i >= 1; i--) { // 倒序枚举,就是经验值最大到小
if (b[a[i].second] < k) { // 第a[i].second,也就是该知识点的已获得的经验值不够k
b[a[i].second] += a[i].first; // 这道题需要做一下,经验值增加
if (b[a[i].second] >= k)
cnt++; // 有多少个知识点达到了k
ans++; // 增加题数(记录使了多少个题目)
c[a[i].second]++; // 统计每个知识点的选择题数
mx = max(mx, c[a[i].second]); // 统计选择最多题的知识点选择了的题数
}
}
// ① 如果最终可以达标的算法数量不够m个,表示无解
// ② 如果选择最多的题数大于题目总数的一半,表示无解
if (cnt < m || n < mx * 2 - 1)
cout << -1 << endl; // 输出-1
else
cout << max(mx * 2 - 1, ans) << endl; // 输出最大值
return 0;
}
```

BIN
GESP/Level6/202412.pdf Normal file

Binary file not shown.

36
GESP/Level6/20241201.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n;
LL s;
string str;
bool out;
int main() {
cin >> n >> s >> str;
for (int i = 0; i < n; i++) {
if (str[i] == 'U') { // 第一种操作
if (s == 1) // 根,不动
continue;
if (out) { // 超过1e12,向上一层
out = false;
continue;
}
s /= 2; // 变成父节点
} else if (str[i] == 'L') { // 第二种操作
if (s * 2 > 1e12) { // 左子结点编号超过1e12,向下一层
out = true;
continue;
}
s *= 2; // 变成左子结点
} else if (str[i] == 'R') { // 第三种操作
if (s * 2 + 1 > 1e12) { // 右子节点编号如果超过1E12,向下一层
out = true;
continue;
}
s = s * 2 + 1; // 变成右子节点
}
}
cout << s << endl;
return 0;
}

2
GESP/Level6/20241201.in Normal file
View File

@@ -0,0 +1,2 @@
3 2
URR

54
GESP/Level6/20241202.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n; // 运输站点数量
int m; // 货车数量
int x; // 两市距离
struct Node { // 运输点
int p; // 运输站点的位置
int c; // 最多容纳车辆数
} q[N];
bool cmp(Node x, Node y) { // 按到A市的距离从小到大排序
return x.p < y.p;
}
vector<int> na, nb; // 需要去靠近A市的车和靠近B市的车
LL ans; // 里程合计
int main() {
cin >> n >> m >> x;
for (int i = 1; i <= n; i++)
cin >> q[i].p >> q[i].c;
sort(q + 1, q + n + 1, cmp); // 按距离A市的距离将运输点排序
for (int i = 1; i <= m; i++) { // 枚举每个货车
int a, b;
cin >> a >> b;
if (a >= b) // 到A市次数不低于到B市次数
na.push_back(a - b); // 只记录A市超过B市的次数
else // 到A市次数低于到B市次数
nb.push_back(b - a); // 只记录B市超过A市的次数
ans += 2ll * min(a, b) * x; // 相等的部分计算距离
}
sort(na.begin(), na.end(), greater<int>()); // 按去A市的次数从大到小排序
sort(nb.begin(), nb.end(), greater<int>()); // 按去B市的次数从大到小排序
int l = 1, r = n; // 最靠近A市的运输点为1,最靠近B市的运输点为n
for (int i = 0; i < na.size(); i++) { // 枚举去A市的车
while (q[l].c == 0)
l++; // 没有容量就去下一个运输点
q[l].c--; // 运输点容量减一
ans += 2ll * q[l].p * na[i]; // 计算路程
}
for (int i = 0; i < nb.size(); i++) { // 枚举去B市的车
while (q[r].c == 0)
r--; // 没有容量就去下一个运输点
q[r].c--; // 运输点容量减一
ans += 2ll * (x - q[r].p) * nb[i]; // 计算路程
}
cout << ans << endl;
return 0;
}

1
GESP/Level6/20241202.md Normal file
View File

@@ -0,0 +1 @@
贪心算法,对于去A市多于去B市的车,尽量安排在靠近A市的运输站,次数差的越多越靠近A市,对于去B市多于去A市的车,尽量安排靠近B市,对于去两市次数相等的车,安排在哪里都一样

BIN
GESP/Level6/202503.pdf Normal file

Binary file not shown.

52
GESP/Level6/202503T1.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int M = 4e5 + 10;
/*
3
1 3
2 3
答案 :
2 2 1
*/
// 链式前向星
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int cnt[2]; // 两种颜色都有多少个
int color[N]; // 当前节点颜色,也可以理解为是奇偶性
/*
u 当前节点
c 当前节点颜色 【二分图染色,也可以理解为是奇偶性】
fa 当前节点的父节点
*/
void dfs(int u, int c, int fa) {
color[u] = c; // u节点标识为c这个颜色
cnt[c]++; // 颜色计数
for (int i = h[u]; ~i; i = ne[i])
if (e[i] != fa) // 不走回头路
dfs(e[i], c ^ 1, u); // 走向下一个节点,下一个节点的颜色需要变换,同时标识父节点是u
}
int main() {
// 初始化链式前向星
memset(h, -1, sizeof h);
int n;
cin >> n;
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
// 深搜一次,从1号节点搜索,标识为0这个颜色,1的父节点不存在,传入0
dfs(1, 0, 0);
// 输出结果
for (int i = 1; i <= n; i++) // 每个节点都讨论一次,其实就是按颜色分两组,每组的个数输出即可
printf("%d ", cnt[color[i]]);
return 0;
}

44
GESP/Level6/202503T2.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 4e5 + 10;
int n;
LL a[N], s[N]; // 原数组,前缀和数组
int q[N], L, R; // 滑动窗口,左端点,右端点
LL ans; // 答案
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[n + i] = a[i]; // 破环成链
}
for (int i = 1; i <= 2 * n; i++) // 一维前缀和
s[i] = s[i - 1] + a[i];
// 滑动窗口
L = R = 1; // 初始化滑动窗口长度为0
ans = -1e18; // 初始化答案为-1e18
for (int i = 1; i <= 2 * n; i++) {
// 维护滑动窗口,确保窗口长度不超过n
while (L <= R && q[L] < i - n)
L++;
// 计算当前位置i与窗口内最小前缀和的差,即为以i结尾的最大子段和
LL currentSum = s[i] - s[q[L]];
ans = max(ans, currentSum);
// 维护单调队列,保证队列中的前缀和是单调递增的
// 如果新的前缀和更小,则队尾的更大值都不可能是最优解,可以删除
while (L <= R && s[i] < s[q[R]])
R--;
// 将当前位置加入队列
q[++R] = i;
}
printf("%lld\n", ans); // 输出答案
return 0;
}

View File

@@ -0,0 +1,30 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 4e5 + 10;
int n;
LL a[N], s[N];
LL ans = -1e18;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[n + i] = a[i];
}
for (int i = 1; i <= 2 * n; i++)
s[i] = s[i - 1] + a[i];
// 对于每个位置i,枚举它前面n个位置,找最小前缀和
for (int i = 1; i <= 2 * n; i++) {
LL minPrefix = s[i - 1]; // 初始化为i-1位置的前缀和
for (int j = max(0, i - n); j <= i - 2; j++)
minPrefix = min(minPrefix, s[j]);
ans = max(ans, s[i] - minPrefix);
}
printf("%lld\n", ans);
return 0;
}

34
GESP/Level6/666.cpp Normal file
View File

@@ -0,0 +1,34 @@
<<<<<<< HEAD
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int v[N], w[N];
int n, L;
int ans = INT_MAX;
int f[N][N];
int main() {
cin >> n >> L;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++)
for (int j = 0; j <= L; j++) {
f[i][j] = f[i - 1][j];
if (j >= v[i])
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
for (int j = 0; j <= L; j++) {
if (f[n][j] >= L) {
ans = j;
break;
}
}
if (ans < INT_MAX)
cout << ans << endl;
else
cout << "no solution" << endl;
return 0;
}
=======
>>>>>>> 13071b821300647688be50739c7b6b190b603339

45
GESP/Level6/P9573.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, p;
queue<int> q[N]; // 模p的余数组成的队列数组
set<int> s; // 集合?
int main() {
int T;
cin >> T;
while (T--) {
cin >> n >> p;
// 如果p太大,就不用算了,因为即使 n+ n-1 也无法构建出一个p,这个可以根据测试用例去想一个特判
if (p > 2 * n - 1) {
for (int i = 1; i <= n; i++)
cout << i << " ";
cout << endl;
continue;
}
for (int i = 2; i <= n; i++) // 按 模p分组
q[i % p].push(i);
for (int i = 0; i < p; i++)
if (!q[i].empty())
s.insert(i); // 队列非空才加入, s里装的是 模p的余数
int x = 1;
cout << x << " "; // 先输出一个 1
for (int i = 2; i <= n; i++) { // 还有n-1个数字需要输出
int f = (p - x % p) % p; // 防止x%p=0的特殊情况
if (q[f].empty())
s.erase(f), f = *s.begin(); // 没了就删除,f换成任意一个非空队列
x = q[f].front();
q[f].pop();
cout << x << " ";
if (q[f].empty())
s.erase(f);
}
cout << endl;
}
return 0;
}

50
GESP/Level6/P9573_2.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, p;
queue<int> q[N]; // 模p的余数组成的队列数组
set<int> s; // 集合?
int main() {
int T;
cin >> T;
while (T--) {
cin >> n >> p;
// 如果p太大,就不用算了,因为即使 n+ n-1 也无法构建出一个p,这个可以根据测试用例去想一个特判
if (p >= 2 * n) {
for (int i = 1; i <= n; i++)
cout << i << " ";
cout << endl;
continue;
}
for (int i = 1; i <= n; i++) // 按 模p分组
q[i % p].push(i);
for (int i = 0; i < p; i++)
if (q[i].size())
s.insert(i); // 队列非空才加入, s里装的是 模p的余数
int x = 0;
while (q[x].empty())
x++;
int y = q[x].front();
q[x].pop();
cout << y << " ";
for (int i = 1; i <= n; i++) { // 还有n-1个数字需要输出
int f = (p - x % p) % p; // 防止x%p=0的特殊情况
if (q[f].empty())
s.erase(f), f = *s.begin(); // 没了就删除,f换成任意一个非空队列
x = q[f].front();
q[f].pop();
cout << x << " ";
if (q[f].empty())
s.erase(f);
}
cout << endl;
}
return 0;
}