2023-2024-1 ACM集训队每周程序设计竞赛(4)题解

文章讨论了编程竞赛中题目表述不清、数据范围遗漏等问题,给出了解题代码和思路,涉及B、C题的错误分析,以及C题使用二进制枚举和D题的等差数列解法,还提及了E题的状态压缩动态规划和F题关于最小操作与最大公因数的讨论。
摘要由CSDN通过智能技术生成

这场B题的题意都没有说清楚以及C题重要的数据范围忘了,影响大家做题了。在这里向大家道个歉。同学们在看完题解后如果有什么不懂的地方以及觉得题解有什么问题的话,可以在企业微信找我。

A:

这题很简单,看看代码。

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
	int a, b, c ;
	cin >> a >> b >> c;
	if (c == 0 && a > b || c != 0 && a >= b)
		cout << "A";
	else
		cout << "B";
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int tt = 1;
//	cin >> tt;
	while (tt--) {
		solve();
	}
	return 0;
}

B:

这题出题人背大锅,忘记将xi代表什么和yi代表什么写上。如果写上,我相信同学们一定能写出来。这题解法就是判断有没有xi小于s并且yi大于d的情况,有的话就是Yes不然就是No。

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
	int n, s, d;
    cin >> n >> s >> d;
    int x, y;
    int fla = 0;
    for (int i = 0; i < n; i++) {
        cin >> x >> y;
        if (x < s && y > d) {
            fla = 1;
        }
    }
    if (fla) {
        cout << "Yes" << endl;
    } else {
        cout << "No" << endl;
    }
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int tt = 1;
//	cin >> tt;
	while (tt--) {
		solve();
	}
	return 0;
}

C:

这题出题人犯了比B题更大的错误,就是忘记将k的数据范围加上,k的数据不加上就是出题人自己也完全没有思路,加上后我们可以发现其实最多一共就是2的16次方种情况。我们只需要枚举这些情况就好了。然后我们可以用二进制来枚举,我们弄一个小于2的k次方的数,如果第一位为1那么我们取c1,否则我们取d2,如果第二位为1那么取c2,否则d2.依次类推,将所有的情况枚举完就可以了。

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
	int n, m;
	cin >> n >> m;
	vector<int> a(m), b(m);
	for (int i = 0; i < m; i++) {
		cin >> a[i] >> b[i];
	}
	int k;
	cin >> k;
	vector<int> c(k), d(k);
	for (int i = 0; i < k; i++) {
		cin >> c[i] >> d[i];
	}
	int ans = 0;
	for (int i = 0; i < (1 << k); i++) {
		vector<int>cnt(n + 1);
		for (int j = 0; j < k; j++) {
			if (i & (1 << j))
				cnt[c[j]]++;
			else
				cnt[d[j]]++;
		}
		int res = 0;
		for (int j = 0; j < m; j++) {
			if (cnt[a[j]] && cnt[b[j]]) {
				res++;
			}
		}
		ans = max(ans, res);
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int tt = 1;
//	cin >> tt;
	while (tt--) {
		solve();
	}
	return 0;
}

D:

这道题,我应该把他放到C题的位置的。其实这题很简单,就是推个式子就好了。相信我们都学过等差数列。这题告诉我们d为1,但是没有限定a1为多少。那么我们根据等差数列的求和公式写出n=a1*i+i*(i-1)/2,,,,,,其中i为数列的长度。我们只需要枚举i。然后如果i*(i-1)大于2*n了的话就break。这样我们求出来的a1都是正数,因为我们发现只要有一个为a1正数就会同时存在一个a1位负数的情况,所以我们每次答案要加2.

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
	int n;
	cin >> n;
	int ans = 0;
	for (int i = 1; i * (i - 1) <= 2 * n; i++) {
		int x = i * (i - 1) / 2;
		x = n - x;
		if (x % i == 0 && x != 0) {
			ans += 2;
		}
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int tt = 1;
//	cin >> tt;
	while (tt--) {
		solve();
	}
	return 0;
}

E:

这道题其实有用到c题的思想,如果c题没有弄懂建议先看c题。这题中,我们要将这个问题转化成图中的问题,然后我们没两个可以相邻的机器人实际上就是连了一条边。我们最终就是要找到包含k个机器人并且机器人个数最小的图。然后我们需要求出k个机器人互相之间的最短路,然后我们用状态压缩dp,用二进制表示。如果其中一位已经被则将二进制对应的位数变成1,最后求出答案。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int inf = 1LL << 60;

void solve() {
	int n, m;
	cin >> n >> m;
	vector<vector<int>> g(n);
	for (int i = 0; i < m; i++) {
		int a, b;
		cin >> a >> b;
		a--;
		b--;
		g[a].push_back(b);
		g[b].push_back(a);
	}
	int k;
	cin >> k;
	vector<int> c(k);
	for (int i = 0; i < k; i++) {
		cin >> c[i];
		c[i]--;
	}
	vector<vector<int>> dist(k, vector<int>(k, inf));
	for (int i = 0; i < k; i++) {
		vector<long long> val(n, inf);
		val[c[i]] = 0;
		queue<int> que;
		que.push(c[i]);
		while (!que.empty()) {
			int now = que.front();
			que.pop();
			for (int to : g[now]) {
				if (val[to] == inf) {
					val[to] = val[now] + 1;
					que.push(to);
				}
			}
		}
		for (int j = 0; j < k; j++) {
			dist[i][j] = val[c[j]];
		}
	}
	vector<vector<int>> dp((1 << k), vector<int>(k, inf));
	for (int i = 0; i < k; i++) {
		dp[(1 << i)][i] = 0;
	}
	for (int s = 1; s < (1 << k); s++) {
		for (int pos = 0; pos < k; pos++) {
			if ((s >> pos) & 1) {
				for (int to = 0; to < k; to++) {
					if (!((s >> to) & 1)) {
						dp[s + (1 << to)][to] = min(dp[s + (1 << to)][to], dp[s][pos] + dist[pos][to]);
					}
				}
			}
		}
	}
	int ans = inf;
	for (int i = 0; i < k; i++) {
		ans = min(ans, dp[(1 << k) - 1][i]);
	}
	cout << (ans == inf ? -1 : ans + 1) << '\n';
	return ;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int tt = 1;
//	cin >> tt;
	while (tt--) {
		solve();
	}
	return 0;
}

F:

我们发现,min 操作实际上就是把两数当中较大的那个删除,较小的那个数不受影响,所以用最小的数删还是用另一个数删是无区别的。

一个性质:gcd(x,y)<=min(x,y)。

不管 amin​ 是原来的还是在 gcd 操作后新生成的,所以无论什么时候删,该删的数都能删掉。问题简化为:取一些数的 gcd,然后用最小的数删,删到只剩一个 gcd。所以我们只需要求去除子集最大公因数的个数即可。那么对于每个 ai​ 都分解因数,对于每个因数,记录有哪些ai​ 包含它。然后再遍历一遍,比对此因数是否与其对应的 ai​ 最大公因数相等即可。

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
	int n, m = 1e18;
	cin >> n;
	vector<int>a(n + 1);
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		m = min(a[i], m);
	}
	map<int, int>g;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j * j <= a[i]; ++j)
			if (a[i] % j == 0) {
				g[j] = __gcd(g[j], a[i]);
				g[a[i] / j] = __gcd(g[a[i] / j], a[i]);
			}
	}
	int ans = 0;
	for (auto it = g.begin(); it != g.end(); ++it) {
		if (it->first <= m)
			ans += (it->first == it->second);
	}
	cout << ans;
	return ;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int tt = 1;
//	cin >> tt;
	while (tt--) {
		solve();
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值