Codeforces Round #696 (Div. 2) A - D 解题报告
A. Puzzle From the
题意:
将 \(a\) 和 \(b\) 两个数(长度相同)相加得到 \(c\),对于 \(c\) 中的如果某一段区间上的数字相同就省略只写一个(例如:\(112200\) 省略为:\(120\))就得到了 \(d\)。在已知 \(b\) 的情况下计算一个 \(a\) 要求使 \(d\) 最大。
思路:
显然对于一个数越长就越大,那么 \(c\) 的相邻位一定是不同的。第一位一定是 \(2\)。
代码:
int main() {
int t; cin >> t;
while (t --) {
int n;
cin >> n;
string a = "", b;
cin >> b;
int len_b = b.size();
a += '1';
for (int i = 1; i < len_b; i ++) {
if (b[i] + '1' == a[i - 1] + b[i - 1]) a += '0';
else a += '1';
}
cout << a << endl;
}
return 0;
}
B. Different Divisors
题意:
对于 \(a\) 的所有因数之差大于等于 \(d\)。求最小的满足条件的 \(a\)。(\(a\) 至少有四个因数)
思路:
对于一个数来说至少有两个因数那就是 \(1\) 和他本身。
那么我们从 \(1\) 开始,找一个 \(\ge d+1\) 的素数 \(x\)。在找一个 \(\ge x+d\) 的素数 \(y\)。那么 \(x\times y\) 就是答案。
之所以找素数是因为如果我们找的是合数,那么对于这个合数还有其他因子,那么就无法判断。
\(x \times y\) 根据质因数分解为:\(x^1 \times y^1\)。那么就只有这四个因数,显然 \(a - y \ge d\) 的。所以合理。
我们可以先预处理出素数,然后二分即可。
代码:
const int N = 1e7;
vector<int> primes;
int is_prime[N];
void init() {
memset(is_prime, 0, sizeof is_prime);
for (int i = 2; i < N; i ++) {
if (is_prime[i] == 0) primes.pb(i);
int len_primes = primes.size();
for (int j = 0; j < len_primes && primes[j] * i < N; j ++) {
is_prime[i * primes[j]] = 1;
if (i % primes[j] == 0) break;
}
}
}
int main() {
init();
int len_primes = primes.size();
int t; cin >> t;
while (t --) {
int d; cin >> d;
int a = lower_bound(all(primes), d + 1) - primes.begin();
int b = lower_bound(all(primes), primes[a] + d) - primes.begin();
ll ans = 1ll * primes[a] * primes[b];
cout << ans << endl;
}
return 0;
}
C. Array Destruction
题意:
给定一个数组 \(a\)。初始选定一个 \(x\),在数组中找两个数 \(a, b\) 满足 \(a + b = x\),那么 \(a,b\) 就从数组中删去,并且 \(x = max(a, b)\),继续操作下去,问是否可以把数组中的数删光?如果可以就输出初始的 \(x\) 和每次删除的 \(a, b\)。
思路:
显然我们可以知道对于当前的操作下选取的 \(a, b\) 中的较大值一定是当前数组中的最大值,只有这样才有可能删完。那么根据当前的 \(x\),又从数组中选出当前的最大值,那么另一个数就可以求出来。只有第一次操作的时候,由于 \(x\) 的未知,所以无法确定另一个数。
由于数组的长度 \(\le 2\times 10^3\)。那么我们可以暴力枚举第一次选取的另一个数。复杂度为 \(O(n^2)\)。
代码:
const int N = 2e3 + 10;
map<int, int> mp;
int a[N];
vector<PII> ans;
int main() {
int t; cin >> t;
while (t --) {
mp.clear();
int n; cin >> n;
int flag = 0;
int m = n << 1;
for (int i = 1; i <= m; i ++) {
cin >> a[i];
mp[a[i]] ++;
}
sort(a + 1, a + m + 1);
mp[a[m]] --;
for (int i = 1; i < m; i ++) {
ans.clear();
ans.pb({a[m], a[i]});
int j = m - 1;
map<int, int> tmp_map = mp;
tmp_map[a[i]] --;
int pre = a[m];
while (j > 0) {
if (tmp_map[a[j]]) {
if (pre - a[j] == a[j] && tmp_map[a[j]] >= 2) {
tmp_map[a[j]] -= 2;
ans.pb({a[j], a[j]});
pre = a[j];
} else if (pre - a[j] != a[j] && tmp_map[pre - a[j]]) {
tmp_map[a[j]] --;
tmp_map[pre - a[j]] --;
ans.pb({a[j], pre - a[j]});
pre = a[j];
} else {
break;
}
}
j --;
}
if (ans.size() == n) {
flag = 1;
break;
}
}
if (flag) {
puts("YES");
cout << ans[0].x + ans[0].y << endl;
for (int i = 0; i < n; i ++) cout << ans[i].x << " " << ans[i].y << endl;
} else {
puts("NO");
}
}
return 0;
}
D. Cleaning
题意:
有 \(n\) 堆石头,每次你可以让相邻位上的石头数 \(-1\)(前提是有石头)。你有一次特殊操作是交换两堆相邻石头。问最终能否把石头的数量都变为 \(0\) ?
思路:
根据题意可知,第 \(x\) 堆石头只能由 \(x+1\) 堆来清除,直至第 \(n\) 堆清楚完第 \(n-1\) 堆后刚好为 \(0\)。
那么 \(O(n^2)\) 的做法就是枚举交换的位置,然后判断是否可行即可。
此时我们可以发现在枚举交换位置后的判断有重复的计算,所以我们可以预处理出 \(l\) 和 \(r\) 数组,\(l[i]\) 代表从左往右消除到位置 \(i\) 上剩余的石头数,\(r[i]\) 代表从右往左消除到位置 \(i\) 上的石头数量,如果 \(l[i] = r[i + 1]\) 就说明不用交换就可以消除完,那么我们枚举交换的位置 \(i, i + 1\),只需要判断四堆石头即:\(l[i - 1], a[i], a[i + 1], r[i + 2]\) 是否可以消除完即可。
注:当 \(l[i] < 0\),那么之后的都是不合法的了。
代码:
const int N = 2e5 + 10;
int a[N];
ll l[N], r[N];
int main() {
int t; cin >> t;
while (t --) {
int n; cin >> n;
memset(l, 0, sizeof l);
memset(r, 0, sizeof r);
int l_flag = n + 1, r_flag = -1;
// LNF 为无穷大
for (int i = 1; i <= n; i ++) {
cin >> a[i];
if (a[i] >= l[i - 1]) l[i] = a[i] - l[i - 1];
else l[i] = LNF / 2;
}
for (int i = n; i >= 1; i --) {
if (a[i] >= r[i + 1]) r[i] = a[i] - r[i + 1];
else r[i] = LNF;
}
int flag = 0;
// 枚举交换的位置
for (int i = 1; i <= n - 1; i ++) {
if (l[i] == r[i + 1]) {
flag = 1;
break;
}
// 交换 i 和 i + 1
if (a[i + 1] >= l[i - 1] && a[i] >= r[i + 2] && a[i + 1] - l[i - 1] == a[i] - r[i + 2]) {
flag = 1;
break;
}
}
if (flag == 1) puts("YES");
else puts("NO");
}
return 0;
}