2023-2024 ICPC German Collegiate Programming Contest (GCPC 2023)

1|0B. Balloon Darts


首先上一些计算几何的板子。

如果k条直线覆盖n个点成立的,则有两种情况。如果nk则一定成立,反之在前k+1个点中必然存在两个点被一条直线经过,我们可以枚举出这条直线,然后暴力的删掉点,然后递归做。

#include <bits/stdc++.h> using namespace std; #define ll long long #define db long double const int N = 1e4 + 7; const db eps = 1e-14; struct Point{ db x,y; Point(db x = 0,db y = 0):x(x),y(y){}; }; using Vec = Point; struct Line{ Point P; Vec v; Line(Point P,Vec v):P(P),v(v){}; }; Vec operator- (Vec u,Vec v){return Vec(u.x - v.x,u.y - v.y);} bool eq(db a,db b){return abs(a - b) < eps;} bool operator == (Vec u,Vec v){return eq(u.x,v.x) and eq(u.y,v.y);} bool on(Point P,Line l){ return eq((P.x - l.P.x) * l.v.y,(P.y - l.P.y) * l.v.x); } Line line(Point A,Point B){return Line(A,B - A);} bool dfs(vector<Point> cur,int k){ if (cur.size() <= k) return true; int ans = 0; for (int i = 0;i <= k;i++){ for (int j = i + 1;j <= k;j++){ Line l = line(cur[i],cur[j]); vector<Point> tmp; for (int z = 0;z < cur.size();z++){ if (!on(cur[z],l)){ tmp.push_back(cur[z]); } } ans |= dfs(tmp,k - 1); } } return ans; } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int n;cin >> n; vector<Point> p(n); for (auto &[x,y] : p){ cin >> x >> y; } if (dfs(p,3)){ cout << "possible\n"; }else{ cout << "impossible\n"; } return 0; }

2|0C. Cosmic Commute


正反两边最短路,然后枚举一下走到哪一个穿越点是上,找到最大的概率。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using vi = vector<int>; i32 main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int n, m, k; cin >> n >> m >> k; vi a(k); for(auto &i : a) cin >> i; vector<vi> e(n + 1); for(int x, y; m; m --) cin >> x >> y, e[x].push_back(y), e[y].push_back(x); auto bfs = [n,e](int x) -> vi { vi dis(n + 1, 1e9), vis(n + 1); dis[x] = 0, vis[x] = 1; queue<int> q; q.push(x); while(not q.empty()) { int u = q.front(); q.pop(); for(auto v : e[u]){ if(vis[v]) continue; dis[v] = dis[u] + 1, vis[v] = 1, q.push(v); } } return dis; }; auto d1 = bfs(1) , d2 = bfs(n); int cnt = 0; for(auto i : a) cnt += d2[i]; int p = d1[n], q = 1; for(auto i : a) { int x = d1[i] * ( k - 1 ) + cnt - d2[i]; if( x * q < p * ( k - 1 ) ){ p = x , q = k - 1; int d = gcd(p , q); p /= d , q /= d; } } cout << p << "/" << q << "\n"; return 0; }

3|0D. DnD Dice


概率 dp,f[i][j]表示前i个骰子掷出j的概率,求解之后排个序就好了。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; #define int long long using vi = vector<int>; using pii = pair<int,int>; const vi p = {4, 6, 8, 12, 20}; i32 main(){ ios::sync_with_stdio(false), cin.tie(nullptr); vi a; for(int x; auto & i : p) { cin >> x; while(x --) a.push_back(i); } int n = a.size(), m = accumulate(a.begin(), a.end(), 0); vector f(n + 1 , vector<double>(m + 1)); f[0][0] = 1; for(int i = 1; i <= n; i ++) for(int j = i; j <= m; j ++) for( int x = 1 ; x <= a[i-1]; x ++) if(j - x >= 0) f[i][j] += f[i-1][j - x] * 1.0 / a[i-1]; vector<pair<double,int>> res; for(int i = n; i <= m; i ++) if(f[n][i] > 0) res.emplace_back(-f[n][i], i); sort(res.begin(), res.end()); for(auto &[x, y] : res) cout << y << " "; return 0; }

4|0E. Eszett


#include <bits/stdc++.h> using namespace std; #define ll long long int main() { ios::sync_with_stdio(false); cin.tie(nullptr); string s; cin >> s; for (auto &x: s) { x = tolower(x); } int n = s.size(); cout << s << endl; for (int i = 0; i + 1 < n; i++) { string tmp = s; if (tmp.substr(i, 2) == "ss") { tmp.replace(i, 2, "B"); cout << tmp << endl; } } return 0; }

5|0F. Freestyle Masonry


具体的贪心策略可以参考官解,就是从最左侧的一列开始竖着放,直到放不下为止,如果空一行就横着放一个,然后下一列继续。这个贪心很好想,但是似乎实现起来还是挺困难的。

然后看了一下题解,思路就是如果当前需要横着放一格,下一行就必须要高度就减 1,如果这一行刚好可以放下,有两种情况,一种是本来就是刚好放下。另一种是上一行横着放,这种情况就要高度加 1 了。这里可以加一和本来高度取一个 max 来解决。

#include <bits/stdc++.h> using namespace std; using i64 = long long; using vi = vector<i64>; int main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n , m; cin >> n >> m; vi h(n); for(auto &hi : h) cin >> hi; if(ranges::max(h) > m) { cout << "impossible\n"; return 0; } int cnt = m; for (int d;auto hi : h) { d = cnt - hi; if(d < 0) break; if(d % 2) cnt --; else cnt = min(cnt + 1, m); } if (cnt == m) cout << "possible\n"; else cout << "impossible\n"; return 0; }

6|0G. German Conference for Public Counting


以四位数举例,首先[0,999]的部分每种数字都需要三个也就是 30 个,然后看四位数的部分,如果有1111,2222,3333,这数字的,就需要额外增加数字,我们可以用n1111计算出需要额外增加几个数字。

#include <bits/stdc++.h> using namespace std; #define ll long long int main(){ ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; if(n < 10) { cout << n + 1 << "\n"; return 0; } int len = log10(n), t = 1; for( int i = 1; i <= len; i ++ ) t = t * 10 + 1; cout << n / t + 10 * len << "\n"; return 0; }

7|0I. Investigating Frog Behaviour on Lily Pad Patterns


把所有可能位置都插入的set,然后把已经存在的删掉,然后每次去里面二分一个新位置跳过去。

#include <bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vector<int> pos(n + 1); set<int> leaf; for(int i = 1; i <= 2e6; i ++) leaf.insert(i); for(int i = 1; i <= n ; i ++) cin >> pos[i], leaf.erase(pos[i]); int q; cin >> q; for(int x, np; q; q--){ cin >> x; np = *leaf.lower_bound(pos[x]); leaf.erase(np), leaf.insert(pos[x]), pos[x] = np; cout << np << "\n"; } return 0; }

8|0L. Loop Invariant


给你一个括号匹配的序列,这个序列本身是在环上的,在环上可以通过任意一个匹配的位置断开,如果断开的后的结果是唯一的输出no,否则输出任意一种其他的断开方式。

对于当前的序列,找到第一个匹配的前缀,然后检查原串是不是可以通过这个前缀反复复制得到,如果是,这断开方法就是唯一的。否则交换前缀后缀就是一种新的断开方式。

#include <bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(false), cin.tie(nullptr); string s; cin >> s; int cnt = 0; for(int i = 0; i < s.size(); i ++) { if(s[i] == '(') cnt ++; else cnt --; if(cnt == 0 and i != s.size() - 1) { string tmp = s.substr(0, i+1); if(s.size() % tmp.size() == 0){ string t = tmp; while(t.size() < s.size()) t += tmp; if( t == s ){ cout << "no"; return 0; } } cout << s.substr(i+1) << tmp; return 0; } } cout << "no"; return 0; }

9|0M. Mischievous Math


因为数字范围只有 100,所以可以直接三次方枚举所有的组合,然后计算出每个数字可以得到的数字集合。

#include <bits/stdc++.h> using namespace std; set<int> op(int x, int y) { set<int> s; s.insert(x + y), s.insert(x - y), s.insert(x * y); s.insert(y - x); if (y != 0 and x % y == 0) s.insert(x / y); if (x != 0 and y % x == 0) s.insert(y / x); return s; } void merge(set<int> &a, const set<int> &b) { for (const auto &i: b) a.insert(i); } int main() { int d; cin >> d; for (int a = 1; a <= 100; a++) { if (a == d) continue; for (int b = a + 1; b <= 100; b++) { if (b == d) continue; for (int c = b + 1; c <= 100; c++) { if (c == d) continue; set<int> A = op(b, c), B = op(a, c), C = op(a, b), T; merge(T, A), merge(T, B), merge(T, C); for (auto i: A) merge(T, op(i, a)); for (auto i: B) merge(T, op(i, b)); for (auto i: C) merge(T, op(i, c)); if (T.count(d)) continue; cout << a << " " << b << " " << c << "\n"; return 0; } } } return 0; }

__EOF__

本文作者PHarr
本文链接https://www.cnblogs.com/PHarr/p/18169799.html
关于博主:前OIer,SMUer
版权声明CC BY-NC 4.0
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
posted @   PHarr  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示