GESP C++ 六级历年真题题解
GESP C++ 六级历年真题题解

1. 下楼梯
题目大意
一共有 个台阶,每一步可以走 个、 个或 个台阶。求走完 个台阶共有多少种不同方案。
解题思路
设 表示走到第 个台阶的方案数。
最后一步可能来自:
- 第 个台阶;
- 第 个台阶;
- 第 个台阶。
因此:
初始化:
表示一个台阶都不走有 种方案。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
LL f[N];
int n;
inline void solve() {
cin >> n;
f[0] = 1;
for (int i = 1; i <= n; i++) {
if (i - 1 >= 0) f[i] += f[i - 1];
if (i - 2 >= 0) f[i] += f[i - 2];
if (i - 3 >= 0) f[i] += f[i - 3];
}
printf("%lld\n", f[n]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
2. 亲朋数
题目大意
给定一个数字串 和正整数 ,统计 的所有连续子串中,有多少个子串对应的整数是 的倍数。子串允许有前导零,相同内容但位置不同的子串算不同子串。
解题思路
设 表示当前处理到某个位置之前,所有 以当前位置前一位结尾 的子串中,模 余数为 的个数。
读入新数字 后:
- 单独由 组成一个新子串;
- 之前所有子串后面接上 ,新余数变为:
每处理一位,就把当前余数为 的子串数量加入答案。
因为 ,所以每个字符枚举所有余数即可。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int p;
string s;
LL cnt[N], tmp[N];
inline void solve() {
cin >> p >> s;
LL Ans = 0;
for (char ch : s) {
int d = ch - '0';
for (int i = 0; i < p; i++) tmp[i] = 0;
tmp[d % p]++;
for (int r = 0; r < p; r++) {
int nr = (r * 10 + d) % p;
tmp[nr] += cnt[r];
}
Ans += tmp[0];
for (int i = 0; i < p; i++) cnt[i] = tmp[i];
}
printf("%lld\n", Ans);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
3. 小杨买饮料
题目大意
有 种饮料,每种饮料最多买一瓶。第 种饮料价格为 ,容量为 。小杨希望购买的总容量不低于 ,并且花费最少。如果无法满足要求,输出 。
解题思路
这是一个 背包问题。
设 表示买到总容量为 时的最小花费。因为只关心是否达到 ,所以容量超过 的都压缩成 。
对于每瓶饮料,倒序更新:
$$dp_{\min(L,j+l_i)}=\min(dp_{\min(L,j+l_i)},dp_j+c_i) $$最后如果 仍然是无穷大,说明无解。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL inf = 4e18;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n, L;
LL dp[N];
inline void solve() {
cin >> n >> L;
for (int i = 0; i <= L; i++) dp[i] = inf;
dp[0] = 0;
for (int i = 1; i <= n; i++) {
LL c, l; cin >> c >> l;
for (int j = L; j >= 0; j--) {
if (dp[j] == inf) continue;
int nj = min((LL)L, j + l);
dp[nj] = min(dp[nj], dp[j] + c);
}
}
if (dp[L] == inf) printf("no solution\n");
else printf("%lld\n", dp[L]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
4. 小杨的握手问题
题目大意
有 名同学,学号为 到 。他们按给定顺序进入教室。每位同学进入时,会和已经在教室内且学号小于自己的同学握手。求总握手次数。
解题思路
对于当前进入的同学 ,他要和之前已经进入且学号小于 的同学握手。
所以问题变成:对每个位置,统计它前面有多少个数比它小,逆序对 变形问题。
可以用树状数组维护已经出现过的学号。
由于学号从 开始,树状数组下标用 。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
LL tr[N];
void add(int x, int v) {
for (; x <= n + 2; x += x & -x) tr[x] += v;
}
LL sum(int x) {
LL s = 0;
for (; x; x -= x & -x) s += tr[x];
return s;
}
inline void solve() {
cin >> n;
LL Ans = 0;
for (int i = 1, x; i <= n; i++) {
cin >> x;
Ans += sum(x);
add(x + 1, 1);
}
printf("%lld\n", Ans);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
也可以用 归并排序 解决,不过和普通的 逆序对 相比,需要使用降序排序。
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
LL a[N], tmp[N];
LL invs(int l, int r) {
if (l >= r) return 0;
int mid = l + (r - l) / 2;
LL Ans = 0;
Ans += invs(l, mid);
Ans += invs(mid + 1, r);
int i = l, j = mid + 1, k = l;
while (i <= mid && j <= r) {
if (a[i] >= a[j]) { // 重点修改这个位置
tmp[k++] = a[i++];
} else {
Ans += (mid - i + 1);
tmp[k++] = a[j++];
}
}
while (i <= mid) tmp[k++] = a[i++];
while (j <= r) tmp[k++] = a[j++];
for (int p = l; p <= r; p++) a[p] = tmp[p];
return Ans;
}
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
printf("%lld\n", invs(1, n));
}
int main() {
int _ = 1;
while (_--) solve();
return 0;
}
5. 闯关游戏
题目大意
游戏有 关,每关有 个通道。第 个通道可以前进 关。离开第 关时,可以获得 分。如果从某关前进后位置不小于 ,则通关。求从第 关开始通关时最多能获得多少分。
解题思路
这是一个有向无环图上的动态规划,因为每次只能向后跳。设 表示到达第 关之前最多已经获得多少分。
从第 关离开时,会获得 分,然后跳到 。如果跳出范围,就更新答案;否则更新后续关卡。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL inf = 4e18;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n, m;
int a[110];
LL b[N], dp[N];
inline void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) cin >> a[i];
for (int i = 0; i < n; i++) cin >> b[i];
for (int i = 0; i <= n; i++) dp[i] = -inf;
dp[0] = 0;
LL Ans = -inf;
for (int i = 0; i < n; i++) {
if (dp[i] == -inf) continue;
for (int j = 1; j <= m; j++) {
int to = i + a[j];
LL val = dp[i] + b[i];
if (to >= n) Ans = max(Ans, val);
else dp[to] = max(dp[to], val);
}
}
printf("%lld\n", Ans);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
6. 工作沟通
题目大意
公司有 名员工, 号是老板。每名员工除了自己以外,还可以被自己的直接领导和间接领导管理。每次给出若干参与合作的员工,要求找一个能够管理所有参与者的员工;如果有多个,输出编号最大的。
解题思路
因为 ,可以直接预处理祖先关系。
设 表示员工 可以管理员工 。
对于每个员工 ,从 不断向父亲走到根,沿途所有节点都可以管理 。
每次询问枚举所有候选员工 ,检查它是否能管理所有参与者,取编号最大者。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n, q;
int fa[310], can[310][310];
inline void solve() {
cin >> n;
fa[0] = -1;
for (int i = 1; i < n; i++) cin >> fa[i];
for (int y = 0; y < n; y++) {
int x = y;
while (x != -1) {
can[x][y] = 1;
x = fa[x];
}
}
cin >> q;
while (q--) {
int m;
cin >> m;
vector<int> v(m);
for (int i = 0; i < m; i++) cin >> v[i];
int Ans = 0;
for (int x = 0; x < n; x++) {
bool ok = true;
for (int y : v) {
if (!can[x][y]) {
ok = false;
break;
}
}
if (ok) Ans = max(Ans, x);
}
printf("%d\n", Ans);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
7. 游戏
题目大意
给定 。每次操作可以把当前 减去 或减去 。当 时游戏结束。求不同操作序列的数量,答案对 取模。如果 ,两种操作也认为不同。
解题思路
设 表示当前数为 时,之后能够形成的操作序列数量。
如果 ,游戏已经结束,只有空操作序列一种:
如果 :
当 或 时,对应部分也算作一种结束方式。
为了处理下标小于 的情况,转移时若减完后 ,直接贡献 。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL mod = 1e9 + 7;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n, a, b, c;
LL f[N];
LL get(int x) {
if (x <= c) return 1;
return f[x];
}
inline void solve() {
cin >> n >> a >> b >> c;
for (int i = 0; i <= c; i++) f[i] = 1;
for (int i = c + 1; i <= n; i++) f[i] = (get(i - a) + get(i - b)) % mod;
printf("%lld\n", f[n]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
8. 好斗的牛
题目大意
有 头牛,每头牛有左攻击范围 和右攻击范围 。若某头牛左边 个牛棚或右边 个牛棚内有其他牛,它就会挑事。现在要保留一段连续牛棚,并把所有牛放进去,使得没有牛会挑事。求最少需要保留多少个牛棚。
解题思路
,可以枚举牛的排列顺序。
如果牛 放在牛 左边并且相邻,那么它们之间的距离至少要满足:
同时也要满足:
所以最小距离为:
对于某个排列,最小保留长度为:
枚举所有排列取最小值。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL inf = 4e18;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
int a[20], b[20], p[20];
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) p[i] = i;
LL Ans = inf;
do {
LL len = 1;
for (int i = 1; i < n; i++) {
int u = p[i];
int v = p[i + 1];
len += max(b[u], a[v]) + 1;
}
Ans = min(Ans, len);
} while (next_permutation(p + 1, p + n + 1));
printf("%lld\n", Ans);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
9. 计算得分
题目大意
给定计分序列 和一个字符串。若一个子串由 个 首尾相接组成,则这个子串可以获得 分。每个字符最多只能被一个计分子串使用。求最大总得分。
解题思路
设 表示只考虑字符串前 个字符时的最大得分。
对于位置 :
- 可以跳过当前字符:
- 如果从 开始的长度为 的子串是 重复 次,那么:
由于 ,每个位置最多尝试 种长度即可。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n, m;
LL a[30], dp[N];
string s;
bool ok(int st, int k) {
if (st + 3 * k > m) return false;
for (int i = 0; i < 3 * k; i++) {
char need = "abc"[i % 3];
if (s[st + i] != need) return false;
}
return true;
}
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> m >> s;
for (int i = 0; i <= m; i++) dp[i] = 0;
for (int i = 0; i < m; i++) {
dp[i + 1] = max(dp[i + 1], dp[i]);
for (int k = 1; k <= n; k++) {
if (ok(i, k)) {
dp[i + 3 * k] = max(dp[i + 3 * k], dp[i] + a[k]);
}
}
}
printf("%lld\n", dp[m]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
10. 二叉树
题目大意
给定一棵以 为根的二叉树,每个节点初始为黑色或白色。每次操作选择一个节点,将它的整棵子树颜色反转。求所有操作完成后每个节点的最终颜色。
解题思路
子树操作可以转化为 序上的区间操作。
先对树做 ,得到每个节点子树对应的区间:
每次反转子树 ,就相当于对区间 异或 。
用差分数组维护:
tag[tin[u]] ^= 1;
tag[tout[u] + 1] ^= 1;
最后按 序做前缀异或,就能知道每个节点被反转了几次。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n, q, tim;
vector<int> g[N];
int tin[N], tout[N], id[N], tag[N], val[N];
string s;
inline void solve() {
cin >> n;
for (int i = 2; i <= n; i++) {
int f;
cin >> f;
g[f].push_back(i);
}
cin >> s;
stack<pair<int, int> > st;
st.push({1, 0});
while (!st.empty()) {
int u = st.top().first;
int op = st.top().second;
st.pop();
if (op == 0) {
tin[u] = ++tim;
id[tim] = u;
st.push({u, 1});
for (int i = (int)g[u].size() - 1; i >= 0; i--) {
st.push({g[u][i], 0});
}
} else {
tout[u] = tim;
}
}
cin >> q;
while (q--) {
int x;
cin >> x;
tag[tin[x]] ^= 1;
tag[tout[x] + 1] ^= 1;
}
int cur = 0;
for (int i = 1; i <= n; i++) {
cur ^= tag[i];
int u = id[i];
val[u] = (s[u - 1] - '0') ^ cur;
}
for (int i = 1; i <= n; i++) {
printf("%d", val[i]);
}
printf("\n");
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
11. 小杨和整数拆分
题目大意
给定正整数 ,要把它拆分成若干个完全平方数之和,求需要的完全平方数的最少数量。
解题思路
这是完全背包模型。
设 表示凑出整数 所需的最少完全平方数个数。
初始化:
枚举所有完全平方数 ,进行完全背包转移:
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10, inf = 1e9;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
int dp[N];
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) dp[i] = inf;
dp[0] = 0;
for (int x = 1; x * x <= n; x++) {
int v = x * x;
for (int i = v; i <= n; i++) {
dp[i] = min(dp[i], dp[i - v] + 1);
}
}
printf("%d\n", dp[n]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
12. 算法学习
题目大意
小杨要学习 种算法。第 道题对应知识点 ,能让该算法掌握程度增加 。每道题最多学一次。要求每种算法掌握程度至少为 ,并且学习顺序中不能连续学习两道相同知识点的题。
求最少学习多少道题;如果不可能,输出 。
解题思路
先对每种算法分别处理。
对于某种算法,为了用尽量少的题达到 ,应该优先选择提升值最大的题。于是把每类题的 从大到小排序,取前若干个直到和至少为 ,得到该算法最少需要的题数 。
设:
如果某类题数量最多为 ,要能排成 不相邻相同 ,必须满足:
如果满足,答案就是 。
否则需要额外选择一些其他类别的题作为 隔板 。若最多的那一类数量为 ,至少需要总题数达到:
缺少的额外题数为:
只要其他类别还有足够没选的题,就可以补齐,否则无解。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int m, n;
LL k;
int a[N];
LL b[N];
vector<LL> v[N];
inline void solve() {
cin >> m >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
cin >> b[i];
v[a[i]].push_back(b[i]);
}
LL S = 0;
int mx = 0;
int id = -1;
vector<int> need(m + 1, 0);
for (int i = 1; i <= m; i++) {
sort(v[i].begin(), v[i].end(), greater<LL>());
LL sum = 0;
int cnt = 0;
while (cnt < (int)v[i].size() && sum < k) {
sum += v[i][cnt];
cnt++;
}
if (sum < k) {
printf("-1\n");
return;
}
need[i] = cnt;
S += cnt;
if (cnt > mx) {
mx = cnt;
id = i;
}
}
if (mx <= S - mx + 1) {
printf("%lld\n", S);
return;
}
LL target = 2LL * mx - 1;
LL extra = target - S;
LL rest = 0;
for (int i = 1; i <= m; i++) {
if (i == id) continue;
rest += (int)v[i].size() - need[i];
}
if (rest >= extra) printf("%lld\n", target);
else printf("-1\n");
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
13. 树上游走
题目大意
有一棵无限二叉树,根节点编号为 。节点 的左儿子为 ,右儿子为 。给定初始节点 和一串操作:
- :移动到父节点,如果已经在根节点则不动;
- :移动到左儿子;
- :移动到右儿子。
求所有操作后所在节点编号。
解题思路
直接模拟即可。
对于当前节点 :
- :如果 ,令 ;
- :令 ;
- :令 。
题目保证最终编号不超过 ,所以用 ,但是并不行,因为有可能会一直往左子树或者右子树走,然后超过了 ,然后再往上走回 以内,也就是说中途会溢出。
所以需要先把操作压缩一下再处理,使用 抵消一下 或者 。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
LL n, s;
char t[N];
deque<char> dq;
inline void solve() {
cin >> n >> s;
scanf("%s", t + 1);
for (int i = 1; i <= n; i++) {
if (t[i] == 'U') {
if (dq.size()) dq.pop_back();
else if (s != 1) s >>= 1;
}
else dq.push_back(t[i]);
}
while (dq.size()) {
char c = dq.front(); dq.pop_front();
if (c == 'L') s <<= 1;
else s = (s << 1) + 1;
}
printf("%lld\n", s);
}
int main() {
int _ = 1;
while (_--) solve();
return 0;
}
14. 运送物资
题目大意
有 个运输站点,第 个站点的位置为 ,最多可以作为 辆车的初始站点。
共有 辆车,第 辆车每天要向 A 市运输 次,向 B 市运输 次。A 市坐标为 ,B 市坐标为 。
如果一辆车放在位置 ,它每天的总路程为:
现在要求为每辆车选择一个初始站点,使所有车辆的总路程之和最小。
解题思路
对于第 辆车,如果它被放在位置 ,费用为:
展开可得:
整理为:
其中 与站点位置 无关,是这辆车的固定贡献。
真正影响选择站点的是:
令:
问题转化为:在满足站点容量限制的前提下,最小化
根据 的正负分情况讨论。
1. 当 时
此时费用项为:
因为 是正数,所以 越小,费用越小。
也就是说,这类车辆应该尽量放在靠近 A 市,也就是位置更小的站点。
并且 越大,对位置越敏感,越应该优先放到更小的位置。
因此:
的车辆按 从大到小排序,从左往右分配站点。
2. 当 时
此时费用项为:
因为 是负数,所以 越大,费用越小。
也就是说,这类车辆应该尽量放在靠近 B 市,也就是位置更大的站点。
并且 越小,负得越多,对位置越敏感,越应该优先放到更大的位置。
因此:
的车辆按 从小到大排序,从右往左分配站点。
3. 当 时
说明:
此时费用为:
与站点位置无关,放在哪里都一样。
所以只需要把固定贡献加入答案即可,不需要专门参与贪心分配。
需要 注意 不能直接把所有车辆按 从大到小排序,然后从左往右分配。
因为题目只保证:
也就是说,站点容量可能有剩余。
如果某辆车 ,它应该被放到靠右的站点,而不是从左边开始分配。
例如只有一辆车只去 B 市,那么它显然应该放在最靠近 B 市的站点。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
struct Sta {
LL p, c;
};
int n, m;
LL x;
vector<Sta> st;
vector<LL> lf, rg;
inline void solve() {
cin >> n >> m >> x;
st.resize(n);
for (int i = 0; i < n; i++) {
cin >> st[i].p >> st[i].c;
}
LL Ans = 0;
for (int i = 0; i < m; i++) {
LL a, b; cin >> a >> b;
Ans += 2 * b * x;
LL w = 2 * (a - b);
if (w > 0) {
lf.push_back(w);
} else if (w < 0) {
rg.push_back(w);
}
}
sort(st.begin(), st.end(), [](Sta A, Sta B) {
return A.p < B.p;
});
sort(lf.begin(), lf.end(), greater<LL>());
sort(rg.begin(), rg.end());
int l = 0, r = n - 1;
for (LL w : lf) {
while (l < n && st[l].c == 0) l++;
Ans += w * st[l].p;
st[l].c--;
}
for (LL w : rg) {
while (r >= 0 && st[r].c == 0) r--;
Ans += w * st[r].p;
st[r].c--;
}
printf("%lld\n", Ans);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
15. 树上漫步
题目大意
给定一棵 个节点的树。对于每个节点,问从它出发,经过偶数步后能够结束漫步的节点有多少个。漫步过程中允许经过重复节点。
解题思路
树是二分图。
从某个节点出发,每走一步,深度奇偶性都会改变。经过偶数步后,只能到达与起点深度奇偶性相同的节点。
反过来,任意两个深度奇偶性相同的节点,它们之间的简单路径长度就是偶数,因此可以到达。
所以只需要统计树上深度为偶数的节点数和深度为奇数的节点数。
对每个节点输出它所属奇偶类的数量。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
vector<int> g[N];
int dep[N], cnt[2];
inline void solve() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
queue<int> q;
q.push(1);
dep[1] = 0;
vector<int> vis(n + 1, 0);
vis[1] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
cnt[dep[u] % 2]++;
for (int v : g[u]) {
if (!vis[v]) {
vis[v] = 1;
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
for (int i = 1; i <= n; i++) {
if (i > 1) printf(" ");
printf("%d", cnt[dep[i] % 2]);
}
printf("\n");
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
16. 环线
题目大意
给定一个环形序列,每个位置有一个快乐值。可以选择环上一段连续路径,至少经过一个车站,且不能重复经过车站。求能获得的最大快乐值。
解题思路
这是最大环形子段和。
有两种情况:
- 不跨过首尾连接处:普通最大子段和;
- 跨过首尾连接处:等价于总和减去中间没有选择的最小子段和。
所以答案为:
但如果所有数都是负数,第二种会变成空段,不合法,此时答案就是最大子段和。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL inf = 4e18;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
LL a[N];
inline void solve() {
cin >> n;
LL sum = 0;
LL mx = -inf, mn = inf;
LL curMax = 0, curMin = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
curMax = max(a[i], curMax + a[i]);
mx = max(mx, curMax);
curMin = min(a[i], curMin + a[i]);
mn = min(mn, curMin);
}
if (mx < 0) printf("%lld\n", mx);
else printf("%lld\n", max(mx, sum - mn));
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
17. 学习小组
题目大意
有 名同学,要划分成若干学习小组。若某个小组恰好有 名同学,则这个小组的积极度为 。求所有划分方案中,积极度总和的最大值。
解题思路
设 表示把 名同学划分成若干小组时的最大积极度。
最后一个小组大小可以是 ,那么:
其中:
这是典型整数拆分 。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
LL a[N], dp[N];
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
dp[i] = 0;
for (int k = 1; k <= i; k++) {
dp[i] = max(dp[i], dp[i - k] + a[k]);
}
}
printf("%lld\n", dp[n]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
18. 最大因数
题目大意
有一棵有根树,根节点为 。对于编号为 的节点,它的父节点是 的最大真因数。给定多组询问 ,求两个节点在树上的距离。
解题思路
一个数 的最大真因数是:
所以从节点 走向根节点的过程,就是每次删掉当前数的最小质因子。
把 分解成质因数,并从小到大排列:
那么它到根的路径依次删除 。
因此两个节点的公共祖先,等价于两个质因数序列从后往前的最长公共后缀。
若两个数的质因数个数分别为 ,公共后缀长度为 ,距离为:
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
vector<int> pri;
int vis[N];
void init() {
for (int i = 2; i < N; i++) {
if (!vis[i]) {
pri.push_back(i);
for (LL j = 1LL * i * i; j < N; j += i) {
vis[j] = 1;
}
}
}
}
vector<int> fac(int x) {
vector<int> v;
for (int p : pri) {
if (1LL * p * p > x) break;
while (x % p == 0) {
v.push_back(p);
x /= p;
}
}
if (x > 1) v.push_back(x);
return v;
}
int q;
inline void solve() {
cin >> q;
while (q--) {
int x, y; cin >> x >> y;
vector<int> a = fac(x);
vector<int> b = fac(y);
int i = (int)a.size() - 1;
int j = (int)b.size() - 1;
int same = 0;
while (i >= 0 && j >= 0 && a[i] == b[j]) {
same++;
i--;
j--;
}
printf("%d\n", (int)a.size() + (int)b.size() - 2 * same);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
init();
int _ = 1;
while (_--) solve();
return 0;
}
19. 划分字符串
题目大意
给定长度为 的小写字母串 ,要把它划分成若干子串。每个子串中每个字母至多出现一次。划分后长度为 的子串价值为 。求最大总价值。
解题思路
因为一个合法子串中每个字母最多出现一次,小写字母只有 个,所以每个合法子串长度最多为 。
设 表示前 个字符的最大价值。
枚举以第 个字符结尾的合法子串长度 ,如果这段子串没有重复字母,则:
每个位置最多向前看 个字符,复杂度 。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
string s;
LL a[N], dp[N];
inline void solve() {
cin >> n;
cin >> s;
s = " " + s;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
dp[i] = 0;
int vis[26] = {0};
for (int j = i; j >= 1 && i - j + 1 <= 26; j--) {
int c = s[j] - 'a';
if (vis[c]) break;
vis[c] = 1;
int len = i - j + 1;
dp[i] = max(dp[i], dp[j - 1] + a[len]);
}
}
printf("%lld\n", dp[n]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
20. 货物运输
题目大意
给定一棵以 号城市为首都的树,边有长度。车队从首都出发,需要经过所有城市,但最后可以不返回首都。求经过道路长度总和的最小值。
解题思路
如果必须从根出发并回到根,那么每条边都要走两次,总长度为:
现在最后可以停在某个城市不回来。为了省最多的路,应该选择从根到某个城市的一条最长路径作为最后不返回的路径。
因此答案为:
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
struct Edge {
int to;
LL w;
};
int n;
vector<Edge> g[N];
LL sumw, mx;
inline void solve() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
LL w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
sumw += w;
}
queue<int> q;
vector<int> vis(n + 1, 0);
vector<LL> dis(n + 1, 0);
q.push(1);
vis[1] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
mx = max(mx, dis[u]);
for (auto e : g[u]) {
int v = e.to;
if (!vis[v]) {
vis[v] = 1;
dis[v] = dis[u] + e.w;
q.push(v);
}
}
}
printf("%lld\n", 2 * sumw - mx);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
21. 路径覆盖
题目大意
给定一棵以 为根的有根树。每个节点染黑有代价 。要求染黑若干节点,使得每条从叶子到根的路径上至少有一个黑色节点。求最小总代价。
解题思路
树形 。
设 表示在以 为根的子树中,保证每条叶子到 的路径上至少有一个黑点的最小代价。
对于节点 :
- 直接染黑 ,代价为 ;
- 不染黑 ,则每个子树都必须自己完成覆盖,代价为:
所以:
叶子节点没有子节点,只能染自己:
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
vector<int> g[N];
LL c[N], dp[N];
inline void solve() {
cin >> n;
for (int i = 2; i <= n; i++) {
int f;
cin >> f;
g[f].push_back(i);
}
for (int i = 1; i <= n; i++) cin >> c[i];
for (int u = n; u >= 1; u--) {
if (g[u].empty()) {
dp[u] = c[u];
} else {
LL sum = 0;
for (int v : g[u]) {
sum += dp[v];
}
dp[u] = min(c[u], sum);
}
}
printf("%lld\n", dp[1]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
22. 道具商店
题目大意
有 件道具,第 件道具可以提升 点攻击力,价格为 。每件道具最多买一次。你有 枚金币,求最多能提升多少攻击力。
解题思路
金币数 可能很大,不能按金币做背包。
但 ,,总攻击力最多为:
所以可以按攻击力做背包。
设 表示获得攻击力 所需的最少金币。
每个道具做 背包:
最后找最大的 ,满足:
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
const LL inf = 4e18;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
LL k;
LL dp[N];
inline void solve() {
cin >> n >> k;
int S = 500 * n;
for (int i = 1; i <= S; i++) dp[i] = inf;
dp[0] = 0;
int mx = 0;
for (int i = 1; i <= n; i++) {
int a;
LL c;
cin >> a >> c;
for (int v = mx; v >= 0; v--) {
if (dp[v] == inf) continue;
dp[v + a] = min(dp[v + a], dp[v] + c);
}
mx += a;
}
int Ans = 0;
for (int v = 0; v <= mx; v++) if (dp[v] <= k) Ans = v;
printf("%d\n", Ans);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
23. 选数
题目大意
给定数组 和 。需要选择若干下标:
并满足:
求所选下标对应的 值之和最大。
解题思路
设 表示只考虑下标 到 时能得到的最大和。
对于位置 ,有两种选择:
- 不选 :
- 选 ,那么下一个可选位置至少是:
所以:
取两者最大即可。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
LL a[N], b[N], dp[N];
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = n; i >= 1; i--) {
int nxt = max((LL)i + 1, (LL)i + b[i]);
LL take = a[i];
if (nxt <= n) take += dp[nxt];
dp[i] = max(dp[i + 1], take);
}
printf("%lld\n", dp[1]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
24. 完全二叉树
题目大意
给定一棵有根二叉树。每个节点都有左儿子 和右儿子 ,不存在则为 。对于每个节点,都有一棵以它为根的子树。求这些子树中有多少棵是完全二叉树。
解题思路
需要同时判断两种性质:
- :以 为根的子树是否为满二叉树;
- :以 为根的子树是否为完全二叉树;
- :子树高度。
空树认为既是满二叉树,也是完全二叉树,高度为 。
对于节点 ,设左儿子为 ,右儿子为 。
满二叉树条件:
完全二叉树有两种合法结构:
- 左子树是满二叉树,右子树是完全二叉树,且左右高度相同;
- 左子树是完全二叉树,右子树是满二叉树,且左子树高度比右子树高 。
即:
$$ok_u=(ok_l\land ok_r)\land \left[ (full_l\land h_l == h_r)\lor(full_r\land h_l == h_r+1) \right] $$最后统计所有 的数量。
代码
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n;
int lch[N], rch[N], h[N];
int full[N], ok[N];
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> lch[i] >> rch[i];
full[0] = 1;
ok[0] = 1;
h[0] = 0;
vector<int> ord;
stack<int> st;
st.push(1);
while (!st.empty()) {
int u = st.top();
st.pop();
ord.push_back(u);
if (lch[u]) st.push(lch[u]);
if (rch[u]) st.push(rch[u]);
}
reverse(ord.begin(), ord.end());
LL Ans = 0;
for (int u : ord) {
int l = lch[u], r = rch[u];
h[u] = max(h[l], h[r]) + 1;
full[u] = full[l] && full[r] && h[l] == h[r];
ok[u] = ok[l] && ok[r] && ((full[l] && h[l] == h[r]) || (full[r] && h[l] == h[r] + 1));
if (ok[u]) Ans++;
}
printf("%lld\n", Ans);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int _ = 1;
while (_--) solve();
return 0;
}
京公网安备11010802045784号