2024 (ICPC) Jiangxi Provincial 省赛
2024 (ICPC) Jiangxi Provincial 省赛
前言
和队友 vp 7t,赛后补了几题。
A. Maliang Learning Painting
思路
输出 a + b + c
代码
cin>>n>>m>>k; cout<<n+m+k<<endl;
C. Liar
思路
队友写得
代码
void solve() { int n,k; cin>>n>>k; int ans=0; vector<int>a(n); for (int i = 0; i <n ; ++i) { cin>>a[i]; } sort(a.begin(), a.end()); for (int i = 0; i <n ; ++i) { ans+=a[i]; } if(ans==k) cout<<n<<endl; else{ cout<<n-1<<endl; } }
D. Magic LCM
思路
数学、质因数分解
考虑 \(a,b\) 互质,则其中一方会变为 \(1\),另一方变为 \(ab\);若 \(a=kx,b=ky\) ,则有一方变为 \(k\),另一方变为 \(kxy\) ,因为 \(xy>x+y\),所以我们应该把质因子尽量地合在一起,且要独立考虑每个质因子的幂次,即将各质因子按幂次从大到小地尽量合,最后就是要么不互质,要么就是 1,所以可以考虑用筛法将质数筛出来,为了降低复杂度,只筛 1e3 以内的,一个小于 1e6 的数不可能产生幂次大于 1 且大于 1e3 的质因子,因此碰到大质数我们直接乘到对应的答案中,小质数需要按照幂次排序再相乘。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; constexpr i64 mod = 998244353; i64 ksm(i64 x, i64 y) { i64 res = 1; while (y) { if (y & 1) res = res * x % mod; x = x * x % mod; y >>= 1; } return res; } //欧拉函数,质数 vector<int> euler_range(int n) { vector<int> phi(n + 1), prime; vector<bool> is_prime(n + 1, true); is_prime[1] = 0, phi[1] = 1; for (int i = 2; i <= n; i++) { if (is_prime[i]) prime.push_back(i), phi[i] = i - 1; for (int j = 0; j < (int)prime.size() && i * prime[j] <= n; j++) { is_prime[i * prime[j]] = 0; if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1); else { phi[i * prime[j]] = phi[i] * prime[j]; break; } } } return prime; } constexpr int N = 1e3; auto pr = euler_range(N); void solve() { int n; cin >> n; vector<vector<int>> mp(pr.back() + 1); vector<i64> res(n + 1, 1); map<int, int> mp2; int noone = 0, a, cnt = 0; for (int i = 1; i <= n; i ++) { cin >> a; for (auto &j : pr) { if (a % j == 0) { cnt = 0; while (a % j == 0) { a /= j; cnt ++; } mp[j].emplace_back(cnt); noone = max(noone, (int)mp[j].size()); } } if (a > 1) { (res[++mp2[a]] *= a) %= mod; noone = max(noone, mp2[a]); } } i64 ans = n - noone; for (auto v : pr) { if (mp[v].size()) { sort(mp[v].begin(), mp[v].end()); } } for (int i = 1; i <= noone; i ++) { for (auto &v : pr) { if (mp[v].size()) { auto mi = mp[v].back(); mp[v].pop_back(); res[i] = res[i] * ksm(v, mi) % mod; } } ans = (ans + res[i]) % mod; } cout << ans << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
E. Magic Subsequence
思路
双向 dfs,三进制枚举(?
注意到一个长度为 n 的正整数序列的子序列的和最多只有 \(2^n\) 种,那么取 \(n = 30\),有 \(2^n > M × n\) 存在,因此本题只需要通过任意 30 个数进行判断。(实际上存在更紧凑的界
对于子集的选取,若有解,一定存在一个子集间互相不交的解,那么 30 个数可以分为三类,属于集合 A 的数,属于集合 B 的数,不在集合中的数。所以可以采用双向 dfs 搜索,不过我这里采取的是三进制枚举,带了个 10 左右的常数,跑满 \(3^{15}\) 会超时,所以只跑了 \(3^{13}\) 和 \(3^{14}\) ,就像上面说的跑不满,选取28会TLE,26会WA,很神奇(,实际写的话还是更推荐 dfs,最后合并的时候可以用归并,也可以哈希一下前半段的差,后半段搜到符合条件的解直接输出即可。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int n; cin >> n; vector<int> a(n + 1); for (int i = 1; i <= n; i ++) cin >> a[i]; n = min(n, 27); int mid = n >> 1; int end1 = 1; for (int i = 1; i <= mid ; i ++) end1 *= 3; vector<pair<int, int>> pre, suf; for (int i = 1; i < end1; i ++) { int sum1 = 0, sum2 = 0; int x = i; for (int j = 1; j <= mid; j ++) { if (x % 3 == 1) sum1 += a[j]; if (x % 3 == 2) sum2 += a[j]; x /= 3; } pre.emplace_back(sum1 - sum2, i); } int end2 = 1; for (int i = 1; i <= n - mid; i++) end2 *= 3; for (int i = 1; i < end2; i ++) { int sum1 = 0, sum2 = 0; int x = i; for (int j = 1 + mid; j <= n; j ++) { if (x % 3 == 1) sum2 += a[j]; if (x % 3 == 2) sum1 += a[j]; x /= 3; } suf.emplace_back(sum2 - sum1, i); } auto print = [ &mid, &n](int pre, int suf)->void{ vector<int> A, B; for (int i = 1; i <= mid; i ++) { if (pre % 3 == 1) A.emplace_back(i); if (pre % 3 == 2) B.emplace_back(i); pre /= 3; } for (int i = 1 + mid; i <= n; i ++) { if (suf % 3 == 1) A.emplace_back(i); if (suf % 3 == 2) B.emplace_back(i); suf /= 3; } cout << A.size() << ' '; for (auto i : A) { cout << i << " \n"[i == A.back()]; } cout << B.size() << ' '; for (auto i : B) { cout << i << " \n"[i == B.back()]; } }; sort(pre.begin(), pre.end()); sort(suf.begin(), suf.end()); int l = 0, r = suf.size() - 1; while (l < pre.size() && r >= 0) { auto [pv, pst] = pre[l]; auto [sv, sst] = suf[r]; if (pv + sv == 0) { print(pst, sst); return; } if (pv < -sv) l ++; else r --; } cout << "-1\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
G. Multiples of 5
思路
队友写得,放个官方题解吧
考虑到对每个位的数独立考虑对 5 取模的余数,假设第 i 位的数为 x,它们的贡献为 \((11^i−1 × x) \bmod 5\)。
对连续长度的同一个数的贡献可以等比数列求和,根据实现细节的不同可能需要使用逆元。
注意到 \(11^i\) 的末位均为 1,因此其对 5 取模的结果均为 1,那么所有数位的贡献都是相等的,直接考虑有多少个当前数计算即可。
代码
void solve() { cin>>n; char x; int s=0; for(int i=1;i<=n;i++){ cin>>a[i]; cin>>x; int t; if(x=='A'){ t=x-'A'+10; }else{ t=x-'0'; } s+=(t*a[i]%5); } if(s%5){ cout<<"No\n"; }else{ cout<<"Yes\n"; } }
H.Convolution
思路
队友写得,放个官解吧
考虑卷积核 \(K\) 中的每个元素产生贡献。\(K\) 中的每个元素都对应矩阵 \(I\) 中的一个子矩阵。
例如 \(K_{i,j}\) 产生的贡献是 \(K_{i,j}×\) 矩阵 $I_{i,j∼n−k+i,m−l+j} $的子矩阵之和,其中的 \(k, l\) 是卷积核 \(K\) 的长宽。
所以当子矩阵和为负数时,\(K_{i,j}\) 取 −1;子矩阵和为正数时,\(K_{i,j}\) 取 1。快速求出某个子矩阵和可以用二维前缀和来实现。
代码
void solve() { cin>>n>>m>>k>>l; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>a[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ b[i][j]=b[i-1][j]+a[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(i<=n-k+1)c[i][j]=c[i][j-1]+b[i][j]; else{ c[i][j]=c[i][j-1]+b[i][j]-b[i-(n-k+1)][j]; } } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(j<=m-l+1)d[i][j]=c[i][j]; else{ d[i][j]=c[i][j]-c[i][j-(m-l+1)]; } } } int ans=0; for(int i=0;i<k;i++){ for(int j=0;j<l;j++){ ans+=abs(d[i+n-k+1][j+m-l+1]); } } cout<<ans<<endl; }
I. Neuvillette Circling
思路
计算几何
实际上所有的最小覆盖圆,都是由这 n 个点中的两个点(作为直径时)或者三个点确定。所以只需要 \(n^2 + n^3\) 分别枚举所有圆,并且再分别计算每个圆覆盖了多少点和它的半径。这样就可以维护所有 k 点覆盖圆中最小的半径。总复杂度为 \(O(n^4)\)。
\(n^2\) 两点距离作为直径,中心作为圆心,\(n^3\) 三点求外切圆。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; constexpr double eps = 1e-8; // 根据题目精度要求进行修改 constexpr double PI = acos(-1.0); // pai, 3.1415916.... int sgn(double x) { // 进行判断, 提高精度 if (fabs(x) < eps) return 0; // x == 0, 精度范围内的近似相等 return x > 0 ? 1 : -1; // 返回正负 } // Need: sgn() typedef struct Point { double x, y; Point(double x = 0, double y = 0) : x(x), y(y) {} // 构造函数, 初始值为 0 // 重载运算符 // 点 - 点 = 向量(向量AB = B - A) Point operator- (const Point &B) const { return Point(x - B.x, y - B.y); } // 点 + 点 = 点, 点 + 向量 = 向量, 向量 + 向量 = 向量 Point operator+ (const Point &B) const { return Point(x + B.x, y + B.y); } // 向量 × 向量 (叉积) double operator^ (const Point &B) const { return x * B.y - y * B.x; } // 向量 · 向量 (点积) double operator* (const Point &B) const { return x * B.x + y * B.y; } // 点 * 数 = 点, 向量 * 数 = 向量 Point operator* (const double &B) const { return Point(x * B, y * B); } // 点 / 数 = 点, 向量 / 数 = 向量 Point operator/ (const double &B) const { return Point(x / B, y / B); } // 判断大小, 一般用于排序 bool operator< (const Point &B) const { return x < B.x || (x == B.x && y < B.y); } // 判断相等, 点 == 点, 向量 == 向量, 一般用于判断和去重 bool operator== (const Point &B) const { return sgn(x - B.x) == 0 && sgn(y - B.y) == 0; } // 判断不相等, 点 != 点, 向量 != 向量 bool operator!= (const Point &B) const { return sgn(x - B.x) || sgn(y - B.y); } } Vector; // Need: (-, *) double dist(Point a, Point b) { return sqrt((a - b) * (a - b)); } // Need: Point() struct Circle { Point o; double r; Circle(Point _o = Point(), double _r = 0) : o(_o), r(_r) {} // 圆的面积 double Circle_S() { return PI * r * r; } // 圆的周长 double circle_C() { return 2 * PI * r; } }; // Need: sgn(), dist() // 点在圆上, 返回 0 // 点在圆外, 返回 -1 // 点在圆内, 返回 1 int Point_with_circle(Point p, Circle c) { double d = dist(p, c.o); if (sgn(d - c.r) == 0) return 0; if (sgn(d - c.r) > 0) return -1; return 1; } // Need: dist() Circle get_circumcircle(Point A, Point B, Point C) { double Bx = B.x - A.x, By = B.y - A.y; double Cx = C.x - A.x, Cy = C.y - A.y; double D = 2 * (Bx * Cy - By * Cx); double x = (Cy * (Bx * Bx + By * By) - By * (Cx * Cx + Cy * Cy)) / D + A.x; double y = (Bx * (Cx * Cx + Cy * Cy) - Cx * (Bx * Bx + By * By)) / D + A.y; Point P(x, y); return Circle(P, dist(A, P)); } int main() { // ios::sync_with_stdio(false); // cin.tie(nullptr); int n; scanf("%d", &n); vector<Vector> p; for (int i = 1; i <= n; i ++) { double x, y; scanf("%lf%lf", &x, &y); p.emplace_back(x, y); } vector<double> ans(n * (n - 1) / 2 + 1, 1e15); for (int i = 0; i < n; i ++) { for (int j = i + 1; j < n; j ++) { Point mid((p[i].x + p[j].x) * 0.5, (p[i].y + p[j].y) * 0.5); double R = dist(mid, p[i]); Circle O(mid, R); int num = 0; for (int k = 0; k < n; k ++) { if (Point_with_circle(p[k], O) != -1)num++; } int op = num * (num - 1) / 2; for (int idx = 1; idx <= op; idx ++) ans[idx] = min(ans[idx], R); } } for (int i = 0; i < n; i ++) { for (int j = i + 1; j < n; j ++) { for (int k = j + 1; k < n; k ++) { Circle O = get_circumcircle(p[i], p[j], p[k]); int num = 0; for (int l = 0; l < n; l ++) { if (Point_with_circle(p[l], O) != -1) num++; } int op = num * (num - 1) / 2; for (int idx = 1; idx <= op; idx ++) ans[idx] = min(ans[idx], O.r); } } } for (int i = 1; i <= n * (n - 1) / 2; i ++) { printf("%.10lf\n", ans[i]); } return 0; }
J.Magic Mahjong
思路
字符串模拟。
需要注意国士无双是 13 种幺九牌各一张再加上其中任意一张,且牌之间是无序的。
需要注意七对子不能有相同的对子。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; set<string> Thirteen, seven; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); string t = "1p9p1s9s1m9m1z2z3z4z5z6z7z"; for (int i = 0; i < t.size(); i += 2) { Thirteen.insert(t.substr(i, 2)); } int n; cin >> n; while (n --) { string s; cin >> s; bool f = 1; set<string> k; for (int i = 0; i < s.size(); i += 2) { string p = s.substr(i, 2); k.insert(p); if (!Thirteen.count(p)) f = 0; } if (f && k.size() == 13) { cout << "Thirteen Orphans\n"; } else if (k.size() == 7) { cout << "7 Pairs\n"; } else { cout << "Otherwise\n"; } } return 0; }
K. Magic Tree
思路
诈骗题。
注意到换行后树实际上截断了,原来一侧的是一条没有选择的链。可以基于这个进行简单的动态规划。
继续观察可以发现,当上次 dfs 时进行了换行后,下一次操作只有一种情况(另一侧延伸是死路),而如果当前没有换行,下一次操作可以选择继续延伸或者不换行。
因为列增加即会导致两种可能,因此方案数为 \(2^{n−1}\)
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; constexpr i64 mod = 998244353; i64 ksm(i64 x, i64 y) { i64 res = 1; while (y) { if (y & 1) res = res * x % mod; x = x * x % mod; y >>= 1; } return res; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); i64 m; cin >> m; cout << ksm(2, m - 1) % mod << '\n'; return 0; }
L. Campus
思路
最短路
时间段最多只有 \(2k+1\) 个,也就是 15 个互不相交的区间和没有大门开的区间,所以只要对这 k 个大门都做一遍 dijkstra ,然后判断哪些时间段有哪些大门开着即可。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; struct DIJ { using i64 = long long; using PII = pair<i64, i64>; vector<i64> dis; vector<vector<PII>> G; DIJ() {} DIJ(int n) { dis.assign(n + 1, 1e18); G.resize(n + 1); } void add(int u, int v, int w) { G[u].emplace_back(v, w); } void dijkstra(int s) { priority_queue<PII> que; dis[s] = 0; que.push({0, s}); while (!que.empty()) { auto p = que.top(); que.pop(); int u = p.second; if (dis[u] < p.first) continue; for (auto [v, w] : G[u]) { if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w; que.push({ -dis[v], v}); } } } } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m, k, T; cin >> n >> m >> k >> T; vector<int> a(n + 1); for (int i = 1; i <= n; i ++) cin >> a[i]; vector<DIJ> dij(k, n); vector<array<int, 3>> door(k); for (auto &[p, l, r] : door) { cin >> p >> l >> r; } for (int i = 0; i < m; i ++) { int u, v, w; cin >> u >> v >> w; for (int j = 0; j < k; j ++) { dij[j].add(u, v, w); dij[j].add(v, u, w); } } for (int i = 0; i < k; i ++) { dij[i].dijkstra(door[i][0]); } vector<i64> dis; auto res = [&](int x)->i64{ if (!x) return -1; vector<i64>(n + 1, 1e18).swap(dis); for (int i = 0; i < k; i ++) { if (x >> i & 1) { for (int j = 1; j <= n; j ++) { dis[j] = min(dis[j], dij[i].dis[j]); } } } i64 res = 0; for (int i = 1; i <= n; i ++) res += a[i] * dis[i]; return res; }; unordered_map<int, i64> ans; for (int t = 1; t <= T; t ++) { int mask = 0; for (int i = 0; i < k; i ++) { auto &[p, l, r] = door[i]; if (l <= t && t <= r) mask |= 1 << i; } if (!ans.count(mask)) { ans[mask] = res(mask); } cout << ans[mask] << '\n'; } return 0; }
本文作者:Ke_scholar
本文链接:https://www.cnblogs.com/Kescholar/p/18335582
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2023-07-31 牛客周赛 Round 4