4047 字
20 分钟
华中农业大学第十五届程序设计竞赛游记及部分题解

华中农业大学第十五届程序设计竞赛游记及部分题解#

游记#

今年第二场线下比赛,赛前几天一直晕晕的,虽然但是,最后还是以六题拿了铜牌,但之后也要加强一些算法的训练了,有些算法的不熟悉导致了很多题目看着没思路,赛后发现实际上思路其实并不是特别困难。赛时快速切了I,D两道签到后,就被E题卡死,直到封榜后没招了,E题写了一位一位的向上模拟侥幸通过~~(实际上是因为模拟写假了且在第一步特判时实际上就把题目写完了所以侥幸过了)。在过E前,过了G,F两题,G因为自己迟迟没有过E心态爆炸而wa了6发,F看出二叉树遍历后写得还算顺利。过了E之后,就去写了L,感觉L还算简单,感觉题解写复杂了。 剩下能写的B和J感觉是因为自己对状压dp和st倍增的不熟悉而没有能写的思路,C看没多少人过也没细看,实际上思路还算清晰吧,感觉这次比赛有些可惜。之后得加训了(上次就说要加训来着)~~。

题解#

I#

题意#

要买nn杯奶茶,买一杯需要aa元,买两杯需要bb元,问最少需要多少钱能恰好买到nn杯奶茶?

代码#

void solve ()
{
i64 n, a, b;
cin >> n >> a >> b;
if (2 * a <= b) {
i64 ans = n * a;
cout << ans << '\n';
}else {
i64 t = n / 2 * b;
if (n & 1) {
t += a;
}
cout << t << '\n';
}
}

D#

题意#

给定整数cc (2c1e9)(2 \leq c \leq 1e9),需要找到两个正整数aabb,满足a+b==ca + b == c,且使lcm(a,b)lcm(a, b)最大。

思路#

对于奇数,直接取n/2\lfloor n / 2 \rfloorn/2\lceil n / 2 \rceil即可。 对于偶数,在n/2n / 2的基础上往下,往上找到lcm(l,r)==l×rlcm(l, r) == l \times r的数即可。

代码#

void solve ()
{
i64 n;
cin >> n;
if (n & 1) {
i64 t1 = n / 2;
i64 t2 = n - n / 2;
i64 ans = t1 * t2;
cout << ans << '\n';
}else {
i64 l = n / 2, r = n / 2;
while (lcm(l, r) != l * r) {
l--;
r++;
}
i64 ans = lcm(l, r);
cout << ans << '\n';
}
}

G#

题意#

给定nn个魔法飞弹,第ii枚飞弹对应第ii个哥布林,给定每个飞弹的穿透力aia_i和伤害cic_i,和每个哥布林的防御力bib_i。当且仅当aibia_i \geq b_i时飞弹才会对哥布林造成伤害。现在有qq次独立的询问,每一次你有一次可以将任意一枚飞弹的穿透力修改为XX的机会,求每一次最多能对哥布林造成多少总伤害?

思路#

我们可以预先处理出不修改穿透力的总伤害,再在剩余的未能造成伤害的飞弹里面先按需要的穿透力,即哥布林的防御力排序,再维护一个前缀数组,能够快速求出能把一个穿透力修改为XX所能多造成的伤害。在处理询问时,仅需在前缀数组中二分查找即可。

时间复杂度:O((n+q)logn)O((n + q)\log n)

代码#

void solve ()
{
int n, q;
cin >> n >> q;
vector <array <i64, 3> > v(n + 1);
for (int i = 1; i <= n; i++) {
cin >> v[i][0];
}
for (int i = 1; i <= n; i++) {
cin >> v[i][1];
}
for (int i = 1; i <= n; i++) {
cin >> v[i][2];
}
i64 res = 0;
vector <array <i64, 2> > a;
a.reserve(n + 1);
for (int i = 1; i <= n; i++) {
if (v[i][0] >= v[i][1]) {
res += v[i][2];
}else {
a.push_back({v[i][1], v[i][2]});
}
}
sort(a.begin(), a.end(), [] (auto aa, auto bb) {
if (aa[0] != bb[0]) {
return aa[0] < bb[0];
}else {
return aa[1] > bb[1];
}
});
i64 m = a.size();
vector <i64> pre(m + 1);
if (!a.empty()) pre[0] = a[0][1];
for (int i = 1; i < m; i++) {
pre[i] = max(pre[i - 1], a[i][1]);
}
while (q--) {
i64 t;
cin >> t;
if (a.empty()) {
cout << res << '\n';
continue;
}
if (!a.empty() && t < a[0][0]) {
cout << res << '\n';
continue;
}
int l = 0, r = m - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (a[mid][0] <= t) {
l = mid + 1;
}else {
r = mid - 1;
}
}
cout << res + pre[r] << '\n';
}
}

F#

题意#

给定一个合法的括号序列ss,按照左括号的顺序对所有括号对进行从11nn的编号,对于编号为pp的括号对,若其左括号后仍为左括号,将下一个左括号的编号ll作为pp的左孩子,若其右括号后为左括号,将下一个左括号的编号rr作为pp的右孩子。现给出二叉树结构,构造出原来的合法括号序列ss

思路#

手玩样例后不难发现,对二叉树进行先序遍历即可。 时间复杂度:O(n)O(n)

代码#

void solve ()
{
int n;
cin >> n;
vector <array <int, 2> > v(n + 1);
for (int i = 1; i <= n; i++) {
cin >> v[i][0] >> v[i][1];
}
vector <int> ans;
auto dfs = [&] (auto self, int fa, int u) -> void {
ans.push_back(u);
if (v[u][0] != 0) self(self, u, v[u][0]);
ans.push_back(u);
if (v[u][1] != 0) self(self, u, v[u][1]);
};
dfs(dfs, 0, 1);
vector <int> cnt(n + 1);
string s;
for (auto x : ans) {
if (cnt[x] == 0) {
s.push_back('(');
cnt[x]++;
}else {
s.push_back(')');
}
}
cout << s << '\n';
}

E#

题意#

给定一个正整数xx (1x1e12)(1 \leq x \leq 1e12),询问是否能找到另一个正整数kk,使得x×kx \times k在十进制中每一个数位上都是99

思路#

(大胆猜测个位上不能是偶数或5) 实际上仅需判断gcd(10,x)=1gcd(10, x) = 1是否成立即可。即xx不能存在质因子2255。 时间复杂度:O(1)O(1)

代码#

void solve ()
{
i64 n;
cin >> n;
i64 t = n % 10;
if (t == 1 || t == 3 || t == 7 || t == 9) {
cout << "YES\n";
}else {
cout << "NO\n";
}
}

L#

题意#

给定一个大小为n×mn \times m的矩阵停车场,每个车位要么是空地(用 . 表示),要么停着一辆固定方向行驶的汽车,方向为:U(上),D(下),L(左),R(右)。若所有车都能够驶离停车场,按顺序输出离开车辆的坐标,否则输出-1。

思路#

简单模拟即可。分别从上到下寻找可以离开停车场的’U’,从下到上寻找可以离开的’D’,从左到右寻找可以离开的’L’ ,从右到左寻找可以离开的’R’,依次记录能够离开的车即可。将U, D, L, R记为一轮寻找。若在一轮寻找中没有车能从成功离开且还有车剩余,输出-1即可。 时间复杂度:O(4×n×m)O(4 \times n \times m)

代码#

void solve ()
{
int n, m;
cin >> n >> m;
vector <vector <char> > v(n + 1, vector <char> (m + 1));
int cnt = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> v[i][j];
if (v[i][j] != '.') cnt++;
}
}
vector <array <int, 2> > ans;
ans.reserve(cnt + 1);
vector <int> v1(m + 1, 1);//u
vector <int> v2(m + 1, n);//d
vector <int> v3(n + 1, 1);//l
vector <int> v4(n + 1, m);//r
int pre = cnt;
while (cnt > 0) {
for (int j = 1; j <= m; j++) {
int i = v1[j];
while (i <= n && (v[i][j] == 'U' || v[i][j] == '.')) {
if (v[i][j] == 'U') {
ans.push_back({i, j});
cnt--;
v[i][j] = '.';
}
i++;
}
v1[j] = i;
}
for (int j = 1; j <= m; j++) {
int i = v2[j];
while (i >= 1 && (v[i][j] == 'D' || v[i][j] == '.')) {
if (v[i][j] == 'D') {
ans.push_back({i, j});
cnt--;
v[i][j] = '.';
}
i--;
}
v2[j] = i;
}
for (int i = 1; i <= n; i++) {
int j = v3[i];
while (j <= m && (v[i][j] == 'L' || v[i][j] == '.')) {
if (v[i][j] == 'L') {
ans.push_back({i, j});
cnt--;
v[i][j] = '.';
}
j++;
}
v3[i] = j;
}
for (int i = 1; i <= n; i++) {
int j = v4[i];
while (j >= 1 && (v[i][j] == 'R' || v[i][j] == '.')) {
if (v[i][j] == 'R') {
ans.push_back({i, j});
cnt--;
v[i][j] = '.';
}
j--;
}
v4[i] = j;
}
if (pre == cnt) {
cout << -1 << '\n';
return;
}
pre = cnt;
}
for (auto [x, y] : ans) {
cout << x << ' ' << y << '\n';
}
}

J#

题意#

给定一个以11为根,包含nn (1n1e5)(1 \leq n \leq 1e5)个节点的树,每个节点ii有一个初始权值mim_i (1mi1e9)(1 \leq m_i \leq 1e9),且对于任意非根节点xx,其权值不会超过其父节点的权值。 现给出qq (1q1e5)(1 \leq q \leq 1e5)次操作。 第一种操作中给出节点uu和权值ww (1w1e9)(1 \leq w \leq 1e9),需要找出第一个权值至少为ww的节点编号,若找不到则输出-1. 第二种操作中给出节点xx和权值vv (1e9w1e9)(-1e9 \leq w \leq 1e9),需要判断给节点xx的权值加上vv后其权值是否超过其父节点的权值或小于其叶节点的权值,若超过其父节点的权值或小于其子节点的权值则修改失败,反之修改成功,并修改权值。

思路#

对于第一种操作,可以用st倍增快速查询,找到最后一个权值小于ww的节点输出其父节点即可。 对于第二种操作,可以使用multiset,对每个节点存下其子节点的权值,将修改后的权值与父节点的权值和最大的子节点的权值比较即可。 时间复杂度:O((n+q)×logn)O((n + q) \times \log n)

代码#

const int m = 20;
void solve ()
{
int n, q;
cin >> n >> q;
vector <i64> val(n + 1);
for (int i = 1; i <= n; i++) {
cin >> val[i];
}
vector <vector <int> > e(n + 1);
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
vector <int> fa(n + 1);
vector <int> order;
order.reserve(n + 1);
fa[1] = 0;
auto dfs = [&] (auto self, int f, int u) -> void {
order.push_back(u);
fa[u] = f;
for (auto v : e[u]) {
if (v == f) continue;
self(self, u, v);
}
};
dfs(dfs, 0, 1);
vector <array <int, m> > st(n + 1);
for (auto u : order) {
st[u].fill(0);
st[u][0] = fa[u];
}
for (int k = 1; k < m; k++) {
for (int i = 1; i <= n; i++) {
st[i][k] = st[st[i][k - 1]][k - 1];
}
}
vector <multiset <i64> > a(n + 1);
for (int i = 2; i <= n; i++) {
a[fa[i]].insert(val[i]);
}
while (q--) {
int op;
cin >> op;
if (op == 1) {
int u;
i64 w;
cin >> u >> w;
if (val[u] >= w) {
cout << u << '\n';
continue;
}
int cur = u;
for (int k = m - 1; k >= 0; k--) {
int t = st[cur][k];
if (t != 0 && val[t] < w) {
cur = t;
}
}
if (fa[cur] == 0) {
cout << -1 << '\n';
}else {
cout << fa[cur] << '\n';
}
}else {
int x;
i64 v;
cin >> x >> v;
bool ok = true;
if (x != 1 && v + val[x] > val[fa[x]]) {
ok = false;
}
if (ok && !a[x].empty()) {
i64 mx = *a[x].rbegin();
if (v + val[x] < mx) {
ok = false;
}
}
if (!ok) {
cout << "FAILED\n";
continue;
}
if (x != 1) {
a[fa[x]].erase(a[fa[x]].find(val[x]));
a[fa[x]].insert(v + val[x]);
}
val[x] += v;
cout << "SUCCESS\n";
}
}
}

B#

题意#

nn (1n20)(1 \leq n \leq 20)个连续的公交车座位,编号从11nn,其中有mm (0mn)(0 \leq m \leq n)个座位是损坏的。Alice和Bob轮流安排乘客入座,Alice先手,并希望入座的乘客尽量多,Bob希望入座的乘客尽量少。给定距离kk,要求任意两名乘客的间距至少大于kk (1k20)(1 \leq k \leq 20)。以字符串的形式输出最终可能的座位状态。

思路#

注意到n20n \leq 20,考虑状压dp。设dp[mask]dp[mask]表示座位占有情况为maskmask时,最优的总入座人数。提前用dfs处理好每一个状态的最优总入座人数:若轮到Alice,其目标为最大化最终人数,枚举每一个合法的ii,即:dp[mask]=maxdp[mask(1i)]dp[mask] = max \, dp[mask \mid (1 \ll i)],若轮到Bob,其目标为最小化最终人数,枚举每一个合法的ii,即:dp[mask]=mindp[mask(1i)]dp[mask] = min \, dp[mask \mid (1 \ll i)]。 接下来需要还原最终方案:可以从mask=0mask = 0开始正向模拟,对于maskmask状态,枚举所有合法的ii,寻找某个dp[mask(1i)]=dp[mask]dp[mask \mid (1 \ll i)] = dp[mask],并将maskmask更新为mask(1i)mask \mid (1 \ll i)后继续查找即可。 时间复杂度:O(n×2n)O(n \times 2^n)

代码#

void solve ()
{
int n, k, m;
cin >> n >> k >> m;
string s = string(n, '.');
for (int i = 0; i < m; i++) {
int t;
cin >> t;
t--;
s[t] = 'x';
}
vector <i64> a(n, 0);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) continue;
if (abs(i - j) <= k) {
a[i] |= (1LL << j);
}
}
}
vector <i64> dp(1LL << n, -1);
auto dfs = [&] (auto self, i64 mask, int cnt) -> i64 {
if (dp[mask] != -1) return dp[mask];
if (cnt & 1) {
dp[mask] = 1e11;
}else {
dp[mask] = -1e11;
}
bool ok = false;
for (int i = 0; i < n; i++) {
if (s[i] == 'x') continue;
if ((mask >> i) & 1) continue;
if ((mask & a[i]) != 0) continue;
ok = true;
i64 nxt = mask | (1LL << i);
i64 val = self(self, nxt, cnt + 1);
if (cnt & 1) {
dp[mask] = min(dp[mask], val);
}else {
dp[mask] = max(dp[mask], val);
}
}
if (!ok) {
dp[mask] = cnt;
}
return dp[mask];
};
dfs(dfs, 0LL, 0LL);
i64 mask = 0LL;
int cnt = 0;
while (1) {
int t = -1;
for (int i = 0; i < n; i++) {
if (s[i] == 'x') continue;
if ((mask >> i) & 1) continue;
if ((mask & a[i]) != 0) continue;
i64 nxt = mask | (1LL << i);
if (dp[nxt] == dp[mask]) {
t = i;
break;
}
}
if (t == -1) break;
if (cnt & 1) {
s[t] = 'b';
}else {
s[t] = 'a';
}
mask |= (1LL << t);
cnt++;
}
cout << s << '\n';
}

C#

题意#

给定大小为n×mn \times m1n,m1e51 \leq n, m \leq 1e5n×m2e5n \times m \leq 2e5)的矩阵,每个格子上都有一个权值aija_{ij} (aij<230)(a_{ij} < 2^{30})。从左上角(1,1)(1, 1)走到右下角(n,m)(n, m),设路径上的地点值以此为a1,a2,...,aka_1, a_2, ... , a_k,定义消耗的体力为OR=a1a2...akOR = a_1 \mid a_2 \mid ... \mid a_k, 定义剩下的物资为AND=a1&a2&...&akAND = a_1 \& a_2 \& ... \& a_k。要求在消耗的体力最小的情况下,最大化剩下的物资。输出最小体力消耗和最大物资剩余。

思路#

先满足体力最小的条件,可以从高到低按位枚举确定答案,处理第ii位时,尝试要求第ii位全部为00。对整张图进行bfs,找出一条路径,其所有的点满足高位贪心结果且第ii位都为00的路径。若不存在,该位只能为11。 再满足最大物资剩余的条件,同样从高到低按位枚举,处理第ii位时,尝试要求第ii位全部为11。在满足体力最小的基础上,找出一条路径,其所有的点满足高位贪心结果且第ii位都为11的路径。若不存在,该位只能为00。 时间复杂度:O(58×n×m)O(58 \times n \times m)

代码#

const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, -1, 0, 1};
void solve ()
{
int n, m;
cin >> n >> m;
vector <vector <i64> > v(n + 1, vector <i64> (m + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> v[i][j];
}
}
i64 ans1 = 0;
int t = 0;
for (int i = 29; i >= 0; i--) {
if (((v[1][1] >> i) & 1) || ((v[1][1] & t) != 0)) {
ans1 += (1LL << i);
continue;
}
vector <vector <int> > vis(n + 1, vector <int> (m + 1, 0));
queue <array <int, 2> > q;
q.push({1, 1});
vis[1][1] = true;
bool ok = false;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
if (x == n && y == m) {
ok = true;
break;
}
for (int j = 0; j < 4; j++) {
int xx = x + dx[j];
int yy = y + dy[j];
if (xx < 1 || xx > n || yy < 1 || yy > m) continue;
if (vis[xx][yy]) continue;
vis[xx][yy] = true;
if (((v[xx][yy] >> i) & 1) || (v[xx][yy] & t) != 0) continue;
q.push({xx, yy});
}
}
if (!ok) {
ans1 |= (1LL << i);
}else {
t |= (1LL << i);
}
}
i64 ans2 = 0;
i64 x = 0;
for (int i = 29; i >= 0; i--) {
if (((ans1 >> i) & 1) == 0) continue;
i64 a = x | (1LL << i);
if ((v[1][1] & t) != 0 || (v[1][1] & a) != a) continue;
vector <vector <int> > vis(n + 1, vector <int> (m + 1, 0));
queue <array <int, 2> > q;
q.push({1, 1});
vis[1][1] = true;
bool ok = false;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
if (x == n && y == m) {
ok = true;
break;
}
for (int j = 0; j < 4; j++) {
int xx = x + dx[j];
int yy = y + dy[j];
if (xx < 1 || xx > n || yy < 1 || yy > m) continue;
if (vis[xx][yy]) continue;
vis[xx][yy] = true;
if ((v[xx][yy] & t) != 0 || (v[xx][yy] & a) != a) continue;
q.push({xx, yy});
}
}
if (ok) {
ans2 |= (1LL << i);
x |= (1LL << i);
}
}
cout << ans1 << ' ' << ans2 << '\n';
}
华中农业大学第十五届程序设计竞赛游记及部分题解
https://blog.everlasting.xin/posts/华中农业大学第十五届程序设计竞赛游记题解/
作者
Everlasting
发布于
2026-04-06
许可协议
CC BY-NC-SA 4.0