2023.7月初模拟赛总结
2023.7月初模拟赛总结
前言:
近期(约)3天比了3场模拟赛,都源于USACO。但是这3场我的成绩都很低,赛后一看题解被自己的智商哭死,实在看不下去了,决定要写一篇总结
Day 1
T1
P3132 [USACO16JAN] Angry Cows G
正解
DP,设为使第个干草堆左边的所有干草堆爆炸的最小力度。
考虑转移方程。
找一个,满足:
会有很多个,我们需要最后一个,即最靠近的那一个。
易得:
其中
类似的,我们可得使右边的爆炸的的状态转移方程。
最后我们枚举爆炸点,显然:
其中,和的互相推进用到了贪心策略。
还有一点,因为最优情况爆炸点只可能在整点或两个整点中间,所以为了避免double精度问题,我们可以都乘以一个。
代码
#include <bits/stdc++.h> using namespace std; #define ll long long #define rp(i, o, p) for (ll i = o; i <= p; ++i) #define pr(i, o, p) for (ll i = o; i >= p; --i) const ll MAXN = 5e4 + 5, INF = 0x3f3f3f3f; ll n; ll f[MAXN], g[MAXN]; ll a[MAXN]; int main() { scanf("%lld", &n); rp(i, 1, n) scanf("%lld", &a[i]), a[i] *= 2; sort(a + 1, a + n + 1); memset(f, 0x3f, sizeof(f)); memset(g, 0x3f, sizeof(g)); ll t = 1; f[1] = 0; rp(i, 2, n) { while (t + 1 < i && a[i] - a[t + 1] > f[t + 1] + 2) ++t; f[i] = min(a[i] - a[t], f[t + 1] + 2); } t = n; g[n] = 0; pr(i, n - 1, 1) { while (t - 1 > i && a[t - 1] - a[i] > g[t - 1] + 2) --t; g[i] = min(a[t] - a[i], g[t - 1] + 2); } ll ans = INF; for (ll i = 1, j = n; i < j;) { ans = min(ans, max((a[j] - a[i]) / 2, max(f[i], g[j]) + 2)); if (f[i + 1] < g[j - 1]) ++i; else --j; } printf("%.1lf", (double)ans / 2); return 0; }
T2
P3133 [USACO16JAN] Radio Contact G
正解
dp,设为在时最小代价。对于他们的行动串,分别用两个递推数组储存他们的横坐标与纵坐标。
代码
#include <bits/stdc++.h> using namespace std; #define ll long long #define rp(i, o, p) for (ll i = o; i <= p; ++i) #define pr(i, o, p) for (ll i = o; i >= p; --i) #define po(x) ((x) * (x)) const ll MAXN = 1e3 + 5; ll n, m; ll fx, fy, cx, cy; ll f[MAXN][MAXN]; ll ax[MAXN], bx[MAXN], ay[MAXN], by[MAXN]; ll d(ll i, ll j) { return po(fx + ax[i] - (cx + bx[j])) + po(fy + ay[i] - (cy + by[j])); } int main() { scanf("%lld%lld", &n, &m); scanf("%lld%lld%lld%lld", &fx, &fy, &cx, &cy); rp(i, 1, n) { char ch; cin >> ch; ax[i] = ax[i - 1], ay[i] = ay[i - 1]; if (ch == 'N') ay[i]++; if (ch == 'S') ay[i]--; if (ch == 'W') ax[i]--; if (ch == 'E') ax[i]++; } rp(i, 1, m) { char ch; cin >> ch; bx[i] = bx[i - 1], by[i] = by[i - 1]; if (ch == 'N') by[i]++; if (ch == 'S') by[i]--; if (ch == 'W') bx[i]--; if (ch == 'E') bx[i]++; } memset(f, 0x3f, sizeof(f)); f[0][0] = 0; rp(i, 0, n) { rp(j, 0, m) { if (i != 0) f[i][j] = min(f[i][j], f[i - 1][j] + d(i, j)); if (j != 0) f[i][j] = min(f[i][j], f[i][j - 1] + d(i, j)); if (i != 0 && j != 0) f[i][j] = min(f[i][j], f[i - 1][j - 1] + d(i, j)); } } printf("%lld\n", f[n][m]); return 0; }
T3
P3134 [USACO16JAN] Lights Out G
正解
按照题意所说,枚举每个点出发的情况。再把路径记录下来,看看这条路径是否唯一。若唯一,则“觉醒”。
我们可以用map储存,路径可以压成string。
其中对于路径储存问题,我们需要知道这条边的长度与这个角的角度。由于这里的角度不是270°就是90°,所以判断旋转方向即可。
代码
#include <bits/stdc++.h> using namespace std; #define ll long long #define rp(i, o, p) for (ll i = o; i <= p; ++i) #define pr(i, o, p) for (ll i = o; i >= p; --i) const ll MAXN = 200 + 5; ll n; struct node { ll x, y; } a[MAXN]; map<string, ll> ma; ll ans; ll len[MAXN], dis[MAXN]; string ang[MAXN], ls[MAXN]; bool got(ll i, ll j, ll k) { bool op; if (a[i].x == a[j].x) { if (a[j].y > a[i].y) { if (a[k].x > a[j].x) op = 1; else op = 0; } else { if (a[k].x > a[j].x) op = 0; else op = 1; } } else { if (a[j].x > a[i].x) { if (a[k].y > a[j].y) op = 0; else op = 1; } else { if (a[k].y > a[j].y) op = 1; else op = 0; } } return op; } inline string work(ll x) { string s; ll a[10], hd = 0; while (x) { a[++hd] = x % 10; x /= 10; } pr(i, hd, 1) s += a[i] + 48; return s; } int main() { scanf("%lld", &n); rp(i, 1, n) scanf("%lld%lld", &a[i].x, &a[i].y); rp(i, 1, n) { ll i0 = i - 1, i1 = i + 1; if (i0 < 1) i0 = n - i0; if (i1 > n) i1 = i1 - n; ang[i] = got(i0, i, i1) ? "A" : "B"; len[i] = abs(a[i0].x - a[i].x) + abs(a[i0].y - a[i].y); } rp(i, 1, n) { ls[i] = work(len[i]); ll t1 = len[1], t2 = len[2]; if (i == 1) continue; rp(j, i + 1, n) t1 += len[j]; pr(j, i - 1, 2) t2 += len[j + 1]; dis[i] = min(t1, t2); } rp(i, 2, n) { string s; s += ang[i]; ma[s]++; rp(j, i + 1, n) { s = s + ls[j] + ang[j]; ma[s]++; } } rp(i, 2, n) { ll now = -1; string s; s += ang[i]; if (ma[s] == 1) continue; ll sum = 0; rp(j, i + 1, n) { s = s + ls[j] + ang[j]; sum += len[j]; if (ma[s] == 1) { now = j; break; } } if (now == -1) ans = max(ans, sum + len[1] - dis[i]); else ans = max(ans, dis[now] + sum - dis[i]); } printf("%lld\n", ans); return 0; }
总结
同年级rk12,倒数第一……
其实题都不难,可能是脑子抽了……
Day 2
T1
P8901 [USACO22DEC] Circular Barn S
正解
这是巴什博奕。
上来先筛一遍质数,欧拉筛搞定。
经过打表找规律会发现一个房间内只要是对于的倍数,则先手必输。(可以证明,但这里不展开了)
对于先手而言,肯定自己占优时希望能快则快、占劣时能拖则拖。
所以我们记录一下轮数(两人都进行一次操作称为一轮)。
如果是的倍数,两人最优策略都只能不停出,所以我们这一个房间结束时,经历了轮。
如果不是,那先手可以取走一个数使得剩下的数右边为的倍数。记录一下最大数。
那么怎么知道答案呢?记录先手获胜时最小轮数与这个房间号,后手同理。
然后比较与的大小,谁更小,意味着谁更快赢得游戏。
想等的话比较谁的房间号更小。
代码
#include <cstdio> using namespace std; #define ll long long #define rp(i, o, p) for (ll i = o; i <= p; ++i) #define pr(i, o, p) for (ll i = o; i >= p; --i) const ll MAXN = 2e5 + 5, MAXM = 5e6 + 5, INF = 0x7f7f7f7f; ll T, n; ll a[MAXN], pri[MAXM], pinum; bool ispr[MAXM]; ll s[4]; ll ans[MAXM]; inline ll read() { ll 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 << 3) + (x << 1) + (ch ^ 48), ch = getchar(); return x * f; } inline void write(ll x) { if (x < 0) { putchar('-'), write(-x); return; } if (x > 9) write(x / 10), putchar(x % 10 + 48); else putchar(x + 48); } void getprime(ll n) { ans[1] = 1; s[1] = 1; for (ll i = 2; i <= n; ++i) { if (!ispr[i]) { pri[++pinum] = i; if (i % 4) s[i % 4] = i; } for (ll j = 1; j <= pinum; ++j) { if (i * pri[j] > n) break; ispr[i * pri[j]] = 1; if (i % pri[j] == 0) break; } if (i % 4 == 0) ans[i] = i / 4 + 1; else ans[i] = (i - s[i % 4]) / 4 + 1; } } int main() { getprime(MAXM - 5); T = read(); while (T--) { n = read(); ll min1 = INF, pos1, min2 = INF, pos2; rp(i, 1, n) { a[i] = read(); if (a[i] % 4 == 0) { if (min2 > ans[a[i]]) { min2 = ans[a[i]]; pos2 = i; } } else { if (min1 > ans[a[i]]) { min1 = ans[a[i]]; pos1 = i; } } } if (min1 < min2) puts("Farmer John"); else if (min1 > min2) puts("Farmer Nhoj"); else { if (pos1 < pos2) puts("Farmer John"); else puts("Farmer Nhoj"); } } return 0; }
T2
P8903 [USACO22DEC] Bribing Friends G
正解
dp,但是需要优化。
的朴素dp这里不说了,直接说正解。
设为遍历了前个人,花了个雪糕可以得到的最大利润。
设为花了元的最大利润。
然后我们枚举在哪一个人身上既花雪糕又花钱。
这显然可以证明只会有一个人既花雪糕又花钱,那他前面的就都用雪糕、后面的就都用钱来支付。
代码
#include <bits/stdc++.h> using namespace std; #define ll long long #define rp(i, o, p) for (ll i = o; i <= p; ++i) #define pr(i, o, p) for (ll i = o; i >= p; --i) const ll MAXN = 2e3 + 5; ll n, A, B; ll f[MAXN][MAXN]; ll g[MAXN][MAXN]; struct node { ll p, c, x, xc; bool operator<(const node &t) const { return x < t.x; } } a[MAXN]; int main() { scanf("%lld%lld%lld", &n, &A, &B); rp(i, 1, n) { ll x, y, z; scanf("%lld%lld%lld", &x, &y, &z); a[i] = { x, y, z, y * z }; } sort(a + 1, a + n + 1); rp(i, 1, n) { rp(j, 0, B) f[i][j] = f[i - 1][j]; pr(j, B, a[i].xc) f[i][j] = max(f[i][j], f[i - 1][j - a[i].xc] + a[i].p); } pr(i, n, 1) { rp(j, 0, A) g[i][j] = g[i + 1][j]; pr(j, A, a[i].c) g[i][j] = max(g[i][j], g[i + 1][j - a[i].c] + a[i].p); } ll ans = 0; rp(i, 1, n) { ans = max(ans, max(f[i - 1][B] + g[i][A], f[i][B] + g[i + 1][A])); for (ll j = 0; j <= min(A, a[i].c); ++j) { if (B < (a[i].c - j) * a[i].x) continue; ans = max(ans, f[i - 1][B - (a[i].c - j) * a[i].x] + g[i + 1][A - j] + a[i].p); } } printf("%lld\n", ans); return 0; }
T3
P8904 [USACO22DEC] Mountains G
正解
暴力膜你模拟。
我们每个山峰都建一个set
,记录他右边能看到的山有哪些。
在修改一座山的数据后,判断山会不会挡住山本来能看见的一些山。若挡住,则将山右边的山从中删掉。
对于山本身,重构就好了。
能互相看到的山的对数在这个过程中也可以统计出来。
代码
#include <cstdio> #include <set> using namespace std; #define ll long long #define rp(i, o, p) for (ll i = o; i <= p; ++i) #define pr(i, o, p) for (ll i = o; i >= p; --i) #define ins insert #define era erase #define upp upper_bound #define low lower_bound const ll MAXN = 2e3 + 5; ll n, ans; ll a[MAXN]; set<ll> s[MAXN]; double gotk(ll i, ll j) { return 1.0 * (a[j] - a[i]) / (j - i); } int main() { scanf("%lld", &n); rp(i, 1, n) scanf("%lld", &a[i]); rp(i, 1, n) { s[i].ins(0), s[i].ins(n + 1); double k = -1e9; rp(j, i + 1, n) { if (gotk(i, j) >= k) { k = gotk(i, j); s[i].ins(j); ++ans; } } } ll Q; scanf("%lld", &Q); while (Q--) { ll ix, h; scanf("%lld%lld", &ix, &h); a[ix] += h; rp(i, 1, ix - 1) { auto it = s[i].low(ix); it--; ll y = *it; if (y && gotk(i, y) > gotk(i, ix)) continue; if (s[i].find(ix) == s[i].end()) s[i].ins(ix), ++ans; for (y = *s[i].upp(ix); y <= n; y = *(s[i].upp(y))) { if (gotk(i, y) >= gotk(i, ix)) break; s[i].era(y), --ans; } } ans -= s[ix].size() - 2; s[ix].ins(0), s[ix].ins(n + 1); double k = -1e9; rp(i, ix + 1, n) { if (gotk(ix, i) >= k) { k = gotk(ix, i); s[ix].ins(i); ++ans; } } printf("%lld\n", ans); } return 0; }
总结
同级rk5,但是大家都没打好,太难了,没有人A。
可能是我这次比较走运。
Day 3
补题去了。
顺便一提,一道绿题做了一天……
其实还是很简单的,但是我的脑细胞却死了不少。。
Day 4
T1
P3090 [USACO13NOV] Empty Stalls G
正解
签到题,但是我没做出来。
赛后看了题解,只要扫一遍就好了,注意要再扫一轮,以免一些特殊情况。
代码
#include <set> #include <cstdio> using namespace std; #define int long long #define rp(i, o, p) for (int i = o; i <= p; ++i) #define pr(i, o, p) for (int i = o; i >= p; --i) const int MAXN = 3e6 + 5, MAXK = 1e4 + 5; int n, k; int num[MAXN]; signed main() { scanf("%lld%lld", &n, &k); rp(i, 1, k) { int x, y, a, b; scanf("%lld%lld%lld%lld", &x, &y, &a, &b); rp(j, 1, y) { int t = a * j % n + b % n; t %= n; num[t] += x; } } rp(i, 0, n - 1) { if (num[i]) { num[(i + 1) % n] += num[i] - 1; num[i] = 1; } } if (num[0]) for (int i = 0; i < n; ++i) { if (num[i]) { num[(i + 1) % n] += num[i] - 1; num[i] = 1; } } rp(i, 0, n - 1) if (!num[i]) { printf("%lld\n", i); break; } return 0; }
T2
P3088 [USACO13NOV] Crowded Cows S
正解
又是签到题,可是我又傻了。
单调队列维护最大值,然后判断即可。
注意正反都做一遍,可以做到。
代码
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; #define int long long #define rp(i, o, p) for (int i = o; i <= p; ++i) #define pr(i, o, p) for (int i = o; i >= p; --i) const int MAXN = 1e5 + 5; int n, d; struct node { int x, h; bool operator<(const node &t) const { return x < t.x; } } a[MAXN], q[MAXN]; bool l[MAXN], r[MAXN]; signed main() { scanf("%lld%lld", &n, &d); rp(i, 1, n) scanf("%lld%lld", &a[i].x, &a[i].h); sort(a + 1, a + n + 1); int lf = 1, rg = 0; rp(i, 1, n) { while (lf <= rg && q[rg].h < a[i].h) --rg; q[++rg] = a[i]; while (lf <= rg && q[lf].x < a[i].x - d) ++lf; if (q[lf].h >= a[i].h * 2) l[i] = 1; } memset(q, 0, sizeof(q)); lf = 1, rg = 0; pr(i, n, 1) { while (lf <= rg && q[rg].h < a[i].h) --rg; q[++rg] = a[i]; while (lf <= rg && q[lf].x > a[i].x + d) ++lf; if (q[lf].h >= a[i].h * 2) r[i] = 1; } int ans = 0; rp(i, 1, n) if (l[i] && r[i])++ ans; printf("%lld\n", ans); return 0; }
T3
正解
dp,容易想到设为第点,从第点跳过来的最大分值。
可得
但是会爆。
若
得
结合一下,得
好像没什么事了。
我们思考一下,如果固定点,点与点的变换类似于共同进行,所以我们可以在同一个循环中搞定。
时间复杂度。
代码
#include <bits/stdc++.h> using namespace std; const int MAXN = 1e3 + 5; int n; struct node { int x, p; bool operator<(const node &t) const { return x < t.x; } } a[MAXN]; int ans; int f[MAXN][MAXN]; signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d%d", &a[i].x, &a[i].p); sort(a + 1, a + n + 1); for (int j = 1; j <= n; ++j) { f[j][j] = a[j].p; int k = j + 1; for (int i = j + 1; i <= n; ++i) { f[i][j] = f[i - 1][j] - a[i - 1].p; while (k > 1 && a[j].x - a[k - 1].x <= a[i].x - a[j].x) f[i][j] = max(f[i][j], f[j][--k]); f[i][j] += a[i].p; ans = max(ans, f[i][j]); } ans = max(ans, f[j][j]); } for (int j = n; j >= 1; --j) { f[j][j] = a[j].p; int k = j - 1; for (int i = j - 1; i >= 1; --i) { f[i][j] = f[i + 1][j] - a[i + 1].p; while (k < n && a[k + 1].x - a[j].x <= a[j].x - a[i].x) f[i][j] = max(f[i][j], f[j][++k]); f[i][j] += a[i].p; ans = max(f[i][j], ans); } ans = max(ans, f[j][j]); } printf("%d\n", ans); return 0; }
总结
同级倒数第一,有两个人AK……
总总结
“开动脑筋。”这是教练常说的话。
确实,这个暑假如果不改变不勤思考的习惯,只会被淘汰。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!