2023-2024 ICPC German Collegiate Programming Contest (GCPC 2023)
B. Balloon Darts
首先上一些计算几何的板子。
如果\(k\)条直线覆盖\(n\)个点成立的,则有两种情况。如果\(n \le k\)则一定成立,反之在前\(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;
}
C. 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;
}
D. 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;
}
E. 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;
}
F. 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;
}
G. German Conference for Public Counting
以四位数举例,首先\([0,999]\)的部分每种数字都需要三个也就是 30 个,然后看四位数的部分,如果有\(1111,2222,3333,\dots\)这数字的,就需要额外增加数字,我们可以用\(\frac{n}{1111}\)计算出需要额外增加几个数字。
#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;
}
I. 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;
}
L. 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;
}
M. 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;
}