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;
}
posted @ 2024-05-01 23:18  PHarr  阅读(61)  评论(0编辑  收藏  举报