07.06&07.11模拟赛总结
07.06&07.11模拟赛总结
前言:
之前学了些新东西,所以只比了两场模拟赛,一场Atcoder
,一场USACO
。
改完题了,趁有空总结一下。
07.06——Day 1
T1
题目描述
在一个维的平面上有个不同的点,编号为。点 的坐标是。
有多少个矩形,使这些点中的个点为顶点,所有的边都平行于或轴?
样例输入 #1
6 0 0 0 1 1 0 1 1 2 0 2 1
样例输出 #1
3
样例输入 #2
4 0 1 1 2 2 3 3 4
样例输出 #2
0
样例输入 #3
7 0 1 1 0 2 0 2 1 2 2 3 0 3 2
样例输出 #3
1
正解
对于每个点,都令他是矩形中左下角的点。我们可以用数组把这个点同横坐标的点都存下来,纵坐标同理。那么再判断右上角那个点是否存在即可。
代码
#include <cstdio> #include <queue> #include <algorithm> #include <map> using namespace std; #define pii pair<int, int> #define ft first #define sd second #define pb push_back const int MAXN = 2e3 + 5; int n; pii a[MAXN]; map<int, map<int, bool>> vis; int cnt; vector<int> fx[MAXN]; vector<int> fy[MAXN]; bool cmp(pii aa, pii bb) { if (aa.ft != bb.ft) return aa.ft < bb.ft; return aa.sd < bb.sd; } signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { int x, y; scanf("%d%d", &x, &y); a[i] = { x, y }; vis[x][y] = 1; } sort(a + 1, a + n + 1, cmp); for (int i = 1; i <= n; ++i) { for (int j = i + 1; j <= n; ++j) { if (a[i].ft == a[j].ft) fx[i].pb(j); if (a[i].sd == a[j].sd) fy[i].pb(j); } } for (int i = 1; i <= n; ++i) { for (auto i1 : fx[i]) { for (auto i2 : fy[i]) { if (vis[a[i2].ft][a[i1].sd]) ++cnt; } } } printf("%d\n", cnt); return 0; }
T2
题目描述
给一个无向图,让你从中选出几个边,要求选出的边权总和最大并且剩下的图要是一个连通图,输出最大的边权。
样例输入 #1
4 5 1 2 1 1 3 1 1 4 1 3 2 2 4 2 2
样例输出 #1
4
样例输入 #2
3 3 1 2 1 2 3 0 3 1 -1
样例输出 #2
1
样例输入 #3
2 3 1 2 -1 1 2 2 1 1 3
样例输出 #3
5
正解
直接kruskal
带走。对于负权边我们也不需要,所以也计算一下花费。
考场忘记kruskal
怎么打了,直接趋势
代码
#include <cstdio> #include <algorithm> using namespace std; #define int long long const int MAXN = 2e5 + 5, INF = 0x3f3f3f3f; int n, m, tot, ans; struct edge { int u, v, w; bool operator<(const edge &t) const { return w < t.w; } } e[MAXN]; int fa[MAXN]; int tp[MAXN], hea; bool vis[MAXN << 1]; inline int rd() { 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 << 3) + (x << 1) + (ch ^ 48), ch = getchar(); return x * f; } void write(int x) { if (x < 0) { putchar('-'), write(-x); return; } if (x > 9) write(x / 10), putchar(x % 10 + 48); else putchar(x + 48); } inline int max(int a, int b) { return a > b ? a : b; } int gfa(int x) { return x == fa[x] ? x : gfa(fa[x]); } int kru() { int val = 0; for (int i = 1; i <= m; ++i) { int rx = gfa(e[i].u), ry = gfa(e[i].v); if (e[i].w < 0) { val += e[i].w; if (rx < ry) fa[ry] = rx; else fa[rx] = ry; } else if (rx == ry) continue; else { if (rx < ry) fa[ry] = rx; else fa[rx] = ry; val += e[i].w; } } return val; } signed main() { n = rd(), m = rd(); for (int i = 1; i <= n; ++i) fa[i] = i; for (int i = 1; i <= m; ++i) { int u = rd(), v = rd(), w = rd(); e[i] = { u, v, w }; tot += w; } sort(e + 1, e + m + 1); ans = tot - kru(); write(max(ans, 0ll)); putchar('\n'); return 0; }
T3
题目描述
给定一张 个点, 条边的有向图,每条边的边权均为 。请对于每一个 求出从点 到 的不经过第 条边的最短路长度。
样例输入 #1
3 3 1 2 1 3 2 3
样例输出 #1
1 2 1
样例输入 #2
4 4 1 2 2 3 2 4 3 4
样例输出 #2
-1 2 3 2
样例输入 #3
5 10 1 2 1 4 1 5 2 1 2 3 3 1 3 2 3 5 4 2 4 3
样例输出 #3
1 1 3 1 1 1 1 1 1 1
样例输入 #4
4 1 1 2
样例输出 #4
-1
正解
可以跑一遍堆优化Dijkstra
,然后找出最短路径(主干路径),对于删掉的边,判断是否为主干路径。如果不是,那对最优答案无影响;否则再跑一遍。
时间复杂度可以证明。
代码
#include <cstdio> #include <queue> using namespace std; #define int long long const int MAXN = 400 + 5, MAXM = 2e5 + 5, INF = 0x3f3f3f3f3f3f3f3f; int n, m; int su, en[MAXM], lt[MAXM], hd[MAXN]; int dis[MAXN]; bool viz[MAXM], vis[MAXN]; int nxt[MAXN][2]; bool isok[MAXM]; struct node { int ix, vl; bool operator>(const node &t) const { if (vl != t.vl) return vl > t.vl; return ix < t.ix; } }; inline int rd() { 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 << 3) + (x << 1) + (ch ^ 48), ch = getchar(); return x * f; } void write(int x) { if (x < 0) { putchar('-'), write(-x); return; } if (x > 9) write(x / 10), putchar(x % 10 + 48); else putchar(x + 48); } inline void add(int u, int v) { en[++su] = v, lt[su] = hd[u], hd[u] = su; } inline int Dij(int x) { priority_queue<node, vector<node>, greater<node>> q; for (int i = 1; i <= m; ++i) viz[i] = (i == x) ? 1 : 0; for (int i = 1; i <= n; ++i) vis[i] = 0, dis[i] = INF; q.push({ 1, 0 }); vis[1] = 1; dis[1] = 0; while (!q.empty()) { int u = q.top().ix; q.pop(); for (int i = hd[u]; i; i = lt[i]) { if (viz[i]) continue; int v = en[i]; if (dis[v] > dis[u] + 1) { nxt[v][0] = u, nxt[v][1] = i; dis[v] = dis[u] + 1; if (!vis[v]) vis[v] = 1, q.push({ v, dis[v] }); } } } return dis[n]; } signed main() { n = rd(), m = rd(); for (int i = 1; i <= m; ++i) { int u = rd(), v = rd(); add(u, v); } int Max = Dij(0); Max = (Max == INF) ? -1 : Max; int tmp = n; while (tmp != 0) { isok[nxt[tmp][1]] = 1; tmp = nxt[tmp][0]; } for (int x = 1, ans; x <= m; ++x) { if (isok[x]) { ans = Dij(x); if (ans == INF) ans = -1; } else ans = Max; write(ans), putchar('\n'); } return 0; }
T4
题目描述
给定一棵树,以及树各节点的点权(点权为偶数)。起初有一个棋子在树的根结点(结点 )处。
- 与 两人轮流操作:将棋子移动到其所在节点的某个叶子节点。
- 到某个节点的得分定义为:棋子经过所有结点点权的中位数。 个数的中位数定义如下:
- 当 为奇数时,中位数为 个数中第 小的值。
- 当 为偶数时,中位数为 个数中第 小与第 小的两数的平均值。
- 希望最后得分尽可能大, 希望最后得分尽可能小。
- 当棋子到达某个叶节点时,游戏结束。
若 先手操作,、 都采取最优策略,求最终得分。
样例输入 #1
5 2 4 6 8 10 4 5 3 4 1 5 2 4
样例输出 #1
7
样例输入 #2
5 6 4 6 10 8 1 4 1 2 1 5 1 3
样例输出 #2
8
样例输入 #3
6 2 2 6 4 6 6 1 2 2 3 4 6 2 5 2 6
样例输出 #3
2
正解
你以为是博弈论,然后开始推SG
,其实并没有什么SG
函数。
因为两人的目标不同,所以对待选择儿子节点时,一个人要最大、另一个人要最小。可以考虑DP
。
仔细想想,发现两人是回合制的,所以对待不同的人操作,有不同的转移方程。
选最大的人:
选最小的人:
其中为的儿子节点。
如何维护中位数呢?方法很多,上至平衡树、下至对顶堆,甚至平板电视。这里给出两个multiset
维护的办法。
代码
#include <bits/stdc++.h> using namespace std; #define pb push_back const int MAXN = 2e5 + 5, INF = 0x3f3f3f3f; int n; int a[MAXN], f[MAXN]; vector<int> G[MAXN]; struct node { multiset<int> s, t; void update() { if (s.size()) { auto it = s.end(); --it; t.insert(*it), s.erase(it); } while (s.size() < t.size()) { auto it = t.begin(); s.insert(*it), t.erase(it); } } void ins(int x) { s.insert(x); update(); } void era(int x) { auto it = s.end(); --it; if (x <= *it) s.erase(s.lower_bound(x)); else t.erase(t.lower_bound(x)); update(); } int query() { auto it = s.end(); --it; if (s.size() > t.size()) return *it; return ((*it) + (*t.begin())) / 2; } } S; void dfs(int x, int fa, int dep) { S.ins(a[x]); bool isok = 0; int minz = INF, maxz = 0; for (auto y : G[x]) { if (y == fa) continue; isok = 1; dfs(y, x, dep + 1); minz = min(minz, f[y]); maxz = max(maxz, f[y]); } if (!isok) f[x] = S.query(); else { if (dep & 1) f[x] = maxz; else f[x] = minz; } S.era(a[x]); } signed main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); for (int i = 1; i < n; ++i) { int u, v; scanf("%d%d", &u, &v); G[u].pb(v), G[v].pb(u); } dfs(1, 1, 1); printf("%d\n", f[1]); return 0; }
总结
174pts/rk11(同级倒数第二)
怎么说呢,本来应该300pts是没问题的。可是T2被kruskal
卡住了,一直在回忆。以至于没有深入思考T3。
这告诉我们,学过的算法知识一定要复习!!
07.11——Day 2
T1
P7990 [USACO21DEC] Closest Cow Wins S
题目描述
Farmer John 沿着一条高速公路拥有一个很长的农场,可以被看作类似于一维数轴。沿着农场有 块草地();第 块草地位于位置 并具有美味值 ()。Farmer John 的死对头 Farmer Nhoj 已经将他的 头奶牛()放在了位置 。所有这些 个位置均是 范围内的不同整数。
Farmer John 需要选择 ()个位置(不一定是整数)放置他的奶牛。这些位置必须与 Farmer Nhoj 的奶牛已经占用的位置不同,但是 Farmer John 可以将他的奶牛放在与草地相同的位置。
拥有最靠近某个草地的奶牛的农夫拥有这一草地。如果来自两方农夫的两头奶牛距这一草地相等,则 Farmer Nhoj 拥有该草地。
给定 Farmer Nhoj 的奶牛的位置以及草地的位置和美味值,求 Farmer John 的奶牛以最优方式放置时可以达到的最大总美味值。
输入格式
输入的第一行包含 、 和 。
以下 行每行包含两个空格分隔的整数 和 。
以下 行每行包含一个整数 。
输出格式
输出一个整数,表示最大总美味值。注意这个问题的答案可能无法用 32 位整数型存储,你可能需要使用 64 位整数型(例如,C 或 C++ 中的 "long long")。
样例输入 #1
6 5 2 0 4 4 6 8 10 10 8 12 12 13 14 2 3 5 7 11
样例输出 #1
36
正解
有感:其实这道题是最难的(蓝)。结果考场死磕这道题,以至于T3(黄)都没仔细想。
解法1
因为有个敌对奶牛,所以道路被分成了个部分。因为我们放奶牛可以放在小数位上而敌对奶牛不行、且敌对奶牛不和草场重合。
所以对于每个部分,我们都只需要两只奶牛就可以将这个部分所有草场的美味度“包揽”。
那如果只是一只奶牛呢?
显然,有一些草场就无法“包揽”。如果我们在这个范围内随便找一个点来放置奶牛,那这只奶牛可以“包揽”的草场只有范围内的草场。
但是我们发现,如果我们进行一下的操作,可得。所以这个范围的长度与奶牛放置点无关。
可以想到什么?一个区间且长度没有变换……
滑动窗口!!
正确的。但是我写了解法2,并且考场也想到了解法2,只是打挂了。
解法2
对于每个草场,左边或右边肯定有一个敌对奶牛,那么我们以草场为圆心求出一个最大半径,使得这个范围内没有敌对奶牛。
那就很容易了,可以得到类似于一条条的线段。将有重合部分的变为同一块“草场”。然后排下序就可以输出了。
代码
此处为解法2
#include <bits/stdc++.h> using namespace std; #define int long long #define pb push_back const int MAXN = 2e5 + 5; int n, m, k; struct node { int p, t; bool operator<(const node &A) const { return p < A.p; } } a[MAXN]; int f[MAXN]; int d[MAXN]; vector<node> ans; bool cmp(node a, node b) { if (a.t != b.t) return a.t > b.t; return a.p < b.p; } signed main() { scanf("%lld%lld%lld", &k, &m, &n); for (int i = 1; i <= k; i++) { int p, t; scanf("%lld%lld", &p, &t); a[i] = { p, t }; } for (int i = 1; i <= m; i++) scanf("%lld", &f[i]); sort(f + 1, f + m + 1); sort(a + 1, a + k + 1); memset(d, 0x3f, sizeof(d)); for (int i = 1; i <= k; i++) { int it = lower_bound(f + 1, f + m + 1, a[i].p) - f; if (it != m + 1) d[i] = min(d[i], f[it] - a[i].p); if (it != 1) d[i] = min(d[i], a[i].p - f[it - 1]); } ans.pb({ a[1].p + d[1] - 1, a[1].t }); for (int i = 2; i <= k; i++) { if (a[i].p - d[i] < ans.back().p) { node tmp = ans.back(); ans.pop_back(); ans.pb({ tmp.p, tmp.t + a[i].t }); } else ans.pb({ a[i].p + d[i] - 1, a[i].t }); } sort(ans.begin(), ans.end(), cmp); int cnt = 0; for (int i = 0; i < n; i++) cnt += ans[i].t; printf("%lld\n", cnt); return 0; }
T2
题目描述
Farmer John 的农场由 块田地()组成,编号为 。在这些田地之间有 条双向道路(),每条道路连接两块田地。
农场有两个牛棚,一个在田地 1 中,另一个在田地 中。Farmer John 希望确保有一种方式可以沿着一组道路在两个牛棚之间行走。 他愿意建造至多两条新道路来实现这一目标。由于田地的位置因素,在田地 和 之间建造新道路的花费是 。
请帮助 Farmer John 求出使得牛棚 和 可以相互到达所需要的最小花费。
输入格式
每个测试用例的输入包含 个子测试用例(),所有子测试用例必须全部回答正确才能通过整个测试用例。
输入的第一行包含 ,之后是 个子测试用例。
每个子测试用例的第一行包含两个整数 和 。以下 行,每行包含两个整数 和 ,表示一条连接两个不同田地 和 的道路。输入保证任何两个田地之间至多只有一条道路,并且所有子测试用例的 之和不超过 。
输出格式
输出 行。第 行包含一个整数,为第 个子测试用例的最小花费。
样例输入 #1
2 5 2 1 2 4 5 5 3 1 2 2 3 4 5
样例输出 #1
2 1
正解
因为只会最多建条边,所以我们有两种方法:
- 直接连接所在连通块与所在连通块,只需连条边
- 通过一个“媒介”连通块来使得与连通,需要连条边
那通过并查集可以搞定连通块,通过set
的二分找出边权尽可能小的。
代码
#include <cstdio> #include <set> #include <algorithm> using namespace std; #define int long long #define po(x) ((x) * (x)) const int MAXN = 5e5 + 5, INF = 1e14; int T, n, m; int fa[MAXN]; set<int> s1, s2; int a[MAXN], b[MAXN]; int gf(int x) { return x == fa[x] ? x : gf(fa[x]); } void un(int u, int v) { int rx = gf(u), ry = gf(v); if (rx > ry) fa[rx] = ry; else fa[ry] = rx; } bool cmp(int x, int y) { int rx = gf(x), ry = gf(y); if (rx != ry) return rx < ry; return x < y; } void solve() { int ans = INF; scanf("%lld%lld", &n, &m); for (int i = 1; i <= n; i++) fa[i] = i; for (int i = 1; i <= m; i++) { int u, v; scanf("%lld%lld", &u, &v); un(u, v); } int r1 = gf(1), rn = gf(n); if (r1 == rn) { puts("0"); return; } s1.clear(), s2.clear(); int tmp = 0, cnt = 0; s1.insert(0), s1.insert(n + 1); s2.insert(0), s2.insert(n + 1); for (int i = 1; i <= n; i++) { int rt = gf(i); if (rt == r1) s1.insert(i); else if (rt == rn) b[++cnt] = i, s2.insert(i); else a[++tmp] = i; } for (int i = 1; i <= cnt; i++) { int v = b[i]; auto it1 = s1.lower_bound(v); --it1; auto it2 = s1.upper_bound(v); int t1 = (*it1), t2 = (*it2); if (t1 != 0) ans = min(ans, po(v - t1)); if (t2 != n + 1) ans = min(ans, po(v - t2)); } sort(a + 1, a + tmp + 1, cmp); int min1 = INF, min2 = INF; for (int i = 1; i <= tmp; i++) { if (i > 1 && gf(a[i]) != gf(a[i - 1])) { ans = min(ans, min1 + min2); min1 = min2 = INF; } auto it1 = s1.lower_bound(a[i]); --it1; auto it2 = s1.upper_bound(a[i]); int t1 = (*it1), t2 = (*it2); if (t1 != 0) min1 = min(min1, po(a[i] - t1)); if (t2 != n + 1) min1 = min(min1, po(a[i] - t2)); it1 = s2.lower_bound(a[i]); --it1; it2 = s2.upper_bound(a[i]); t1 = (*it1), t2 = (*it2); if (t1 != 0) min2 = min(min2, po(a[i] - t1)); if (t2 != n + 1) min2 = min(min2, po(a[i] - t2)); } ans = min(ans, min1 + min2); printf("%lld\n", ans); } signed main() { scanf("%lld", &T); while (T--) solve(); return 0; }
T3
P7992 [USACO21DEC] Convoluted Intervals S
题目描述
奶牛们正在努力尝试发明有趣的新游戏来玩。他们目前的工作之一与一组 个区间()有关,其中第 个区间从数轴上的 位置开始,并在位置 结束。 和 均为 范围内的整数,其中 。
这个游戏的玩法是,Bessie 选择某个区间(假设是第 个区间),而她的表妹 Elsie 选择某个区间(假设是第 个区间,可能与 Bessie 所选的的区间相同)。给定某个值 ,如果 ,则她们获胜。
对范围 内的每个值 ,请计算使得 Bessie 和 Elsie 可以赢得游戏的有序对 的数量。
输入格式
输入的第一行包含 和 。以下 行每行以整数 和 的形式描述一个区间。
输出格式
输出 行,依次包含范围 中的每一个 的答案。
样例输入 #1
2 5 1 3 2 5
样例输出 #1
0 0 1 3 4 4 4 3 3 1 1
正解
用到了差分。
发现老小了,那就思考从这方面做文章。
我们发现,这个一组与一组可以给出贡献为,为桶,统计起点出现了几遍。
如果你打过25pts部分分的暴力的话,你就明白为什么要用差分。这里不展开了。
代码
#include <bits/stdc++.h> using namespace std; #define int long long const int MAXN = 2e5 + 5; int n, m; int a[MAXN], b[MAXN]; int f[MAXN << 1]; signed main() { scanf("%lld%lld", &n, &m); for (int i = 1; i <= n; i++) { int x, y; scanf("%lld%lld", &x, &y); a[x]++, b[y]++; } for (int i = 0; i <= m; i++) { for (int j = 0; j <= m; j++) { f[i + j] += a[i] * a[j]; f[i + j + 1] -= b[i] * b[j]; } } for (int i = 0, ans = 0; i <= m + m; i++) { ans += f[i]; printf("%lld\n", ans); } return 0; }
总结
124pts/rk6
还不错,可是还不是我能到达的最大高度,这可能是个良好开端。
总总结
用日日新的态度来学竞赛,收获真的很大!
借这次良好开端,下次“扶摇直上九万里”!
怎么说AKCSP-J
、CSP-S
拿个一等吧
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】