oj: CodeForces
题目解析采用倒叙。
D. Cleaning
oj: CodeForces
题意
给你一个包含 n n n 个元素的数组,你可以执行以下操作任意次:
选择两个挨着的正整数,使他们分别减一。
注意当一个数字减为 0 0 0 之后,他旁边的两个数字并不相邻。
你可以选择在开始前做一次额外的操作:选择两个相邻的数字使之互换位置。
判断是否有可能把数组全部减为 0 0 0。
题解
假设数组为 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,当消去 a 1 a_1 a1 后 a 2 a_2 a2 变成 a 2 − a 1 a_2-a_1 a2−a1。当消去前两项后 a 3 a_3 a3 变成 a 3 − a 2 + a 1 a_3-a_2+a_1 a3−a2+a1,以此类推。
我们把消去前 i − 1 i-1 i−1 项时的第 i i i 项定义为 b b b 数组。那么要想使数组全部消去,需要保证 b b b 数组的每一个元素都大于等于 0 0 0,且 b b b 数组的最后一个元素是 0 0 0。
首先检查不交换能不能消去全部的元素。
然后考虑交换两个数字后的影响,假设现在交换 a i a_i ai 和 a i + 1 a_{i+1} ai+1,这次交换对前面的数字是没有影响的;对后面的影响分奇偶性考虑。
第 i i i 项由 a i a_i ai 变成 a i + 1 a_{i+1} ai+1,增加值为 a i + 1 − a i a_{i+1}-a_i ai+1−ai,第 i i i 项后面的元素的增加值是 2 ( a i − a i + 1 ) 2(a_i-a_{i+1}) 2(ai−ai+1) 和 − 2 ( a i − a i + 1 ) -2(a_i-a_{i+1}) −2(ai−ai+1) 随着奇偶性交替出现。和 i + 1 i+1 i+1 相同奇偶性的增加值是 2 ( a i − a i + 1 ) 2(a_i-a_{i+1}) 2(ai−ai+1),否则是 − 2 ( a i − a i + 1 ) -2(a_i-a_{i+1}) −2(ai−ai+1)。
那么我们按照奇偶性保存后缀的最小值,然后枚举交换的数字,看最小值加上对应奇偶性位置的增加值是否小于 0 0 0 即可。如果不小于 0 0 0 就说明交换后后面的 b b b 数组元素都不小于 0 0 0,否则就说明后面没办法全部消去。而且由于交换只对后面的数字有效,所以交换 i i i 位置和 i + 1 i+1 i+1 位置的数字时,必须保证 i i i 前面的 b b b 数组元素都是大于等于 0 0 0 的。
综上,我们需要枚举交换的位置,且交换的位置不能在 b b b 数组中的负数位置之后。枚举时需要保证 a i + 1 − a i ≥ 0 a_{i+1}-a_i\ge 0 ai+1−ai≥0,对应奇偶性位置的最小值加上增加值大于等于 0 0 0, b b b 数组最后一个元素为 0 0 0。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 200005;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n;
int a[maxn]; // 原数组
int b[maxn]; // 消去所有前面的元素后自己的剩余值
int mi[maxn]; // 对应奇偶性位置的后缀最小值
int sol() {
_for(i, n) a[i] = read(), b[i] = a[i];
int f = 1; // 标记是否可以不用任何而交换就消去所有的元素
for(int i = 1; i < n; ++i) {
b[i] -= b[i - 1];
if(b[i] < 0) f = 0;
}
if(f && b[n - 1] == 0) return 1; // 可以直接消去所有的元素,返回成功
mi[n - 1] = b[n - 1];
mi[n - 2] = b[n - 2];
for(int i = n - 3; i >= 0; --i) mi[i] = min(mi[i + 2], b[i]); // 求出对应奇偶性位置的后缀最小值
int p = 0;
while(p + 2 < n && b[p] >= 0) ++p; // 由于交换只对i及i后面的元素有影响,所以必须保证i前面的b大于等于0
for(int i = 0; i <= p; ++i) {
int t = a[i] - a[i + 1]; // 交换形成的差
if(b[i] - t < 0) continue;
if(mi[i + 1] + 2 * t >= 0 && (i + 2 < n && mi[i + 2] - 2 * t >= 0 || i + 2 >= n)) {
// 如果交换后后面所有的b都大于等于0则有可能成功
if(b[n - 1] + ((i & 1) == ((n - 1) & 1) ? -1 : 1) * 2 * t == 0) return 1; // 如果交换后满足上面的条件并且最后一个b为0,就代表可以全部消去
}
}
return 0;
}
int main() {
int T = read();
_for(i, T) {
n = read();
printf("%s\n", sol() ? "YES":"NO");
}
return 0;
}
C. Array Destruction
oj: CodeForces
题意
给你一个长度为 2 n 2n 2n 的数组,最开始你可以选择一个任意的正整数 x x x。然后反复执行以下操作:
从数组的剩余元素中选出两个,并且这两个数的和要等于 x x x。
让这两个数中较大的那个作为新的 x x x。
判断是否有可能把数组中全部的元素消去。如果有可能就输出一个合法的方案。
题解
首先第一轮中的两个数字之一是可以确定的,第一轮选的两个数一定包含数组的最大值,第二个则不确定。且之后的每一轮一定都包含数组的剩余元素的最大值。
并且一旦前一轮的两个值确定后,后一轮的数字也是确定的。假设上一轮选的较大的数是 a a a,那么这一轮要选的数字就是剩下的最大值 m m m 和 a − m a-m a−m。那么只要第一轮的数字确定后,后面所有的递推过程都已经确定了。
那么唯一不确定的就是第一轮选的除了最大值的另一个数。我们只需要枚举第一轮选的那个数字,然后尝试递推一遍试试能不能成功即可。
至于如何尝试递推,先对a数组排个序,统计一下每个数字的出现次数,然后从大到小选即可。
总时间复杂度 O ( n 2 ) O(n^2) O(n2)。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n;
vector<int> a;
int vis[1000005];
vector<int> path;
void init() {
a.clear();
path.clear();
}
int che(int sum) {
int p = 2 * n - 2;
_for(i, n - 1) {
while(p >= 0 && !vis[a[p]]) --p;
if(p < 0) return 0;
path.push_back(a[p]);
--vis[a[p]];
if(!vis[sum - a[p]]) return 0;
path.push_back(sum - a[p]);
--vis[sum - a[p]];
sum = a[p];
}
return 1;
}
void sol() {
init();
_for(i, 2 * n) a.push_back(read()), ++vis[a[i]];
sort(a.begin(), a.end());
int ans = 0;
_for(i, 2 * n - 1) {
while(path.size()) {
++vis[path.back()];
path.pop_back();
}
path.push_back(a.back());
--vis[a.back()];
--vis[a[i]];
path.push_back(a[i]);
if(ans = che(a.back())) break;
}
if(ans) {
printf("YES\n%d\n", path[0] + path[1]);
for(int i = 0; i < path.size(); i += 2) printf("%d %d\n", path[i], path[i + 1]);
}
else printf("NO\n");
_for(i, 2 * n) vis[a[i]] = 0;
}
int main() {
int T = read();
_for(i, T) {
n = read();
sol();
}
return 0;
}
B. Different Divisors
oj: CodeForces
题意
找出满足以下条件的最小的a:
- a至少有4个除数。
- a的任意两个除数的差至少是d。
题解
首先不管 a a a 的值是什么, 4 4 4 个除数中第一个除数可以是 1 1 1,第 4 4 4 个除数可以是 a a a 本身。所以我们只需要找出另外两个除数就能确定 a a a 了。
要使 a a a 尽可能小,则 a a a 的除数也需要尽可能小。所以第 2 2 2 个除数是和 1 1 1 相差至少 d d d 的第一个素数。
至于为什么是素数,如果 a a a 可以被 c c c 整除,那么 c c c 需要和 1 1 1 至少相差 d d d,考虑到我们取尽可能小的数作为除数,则我们将 c c c 作为第 2 2 2 个除数,如果 c c c 也不是素数,则 c c c 的除数需要和 1 1 1 至少相差 d d d ,则我们将 c c c 的除数作为第 2 2 2 个除数,由此循环,我们一定取的是个素数作为第 1 1 1 个除数。
第 3 3 3 个除数是和第 2 2 2 个除数相差至少为 d d d 的第一个素数。
所以答案是第 2 2 2 个除数乘以第 3 3 3 个除数。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 100005;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
vector<int> arr;
int vis[1000006];
void doit(int maxnum) {
for(int i = 2; i <= maxnum; ++i) {
if(!vis[i]) arr.push_back(i);
for(int j = 0; j < arr.size() && arr[j] * i <= maxnum; ++j) {
vis[arr[j] * i] = 1;
if(i % arr[j] == 0) break;
}
}
}
int d;
int main() {
doit(1000001);
int T = read();
_for(i, T) {
d = read();
LL a = *lower_bound(arr.begin(), arr.end(), d + 1);
LL b = *lower_bound(arr.begin(), arr.end(), a + d);
printf("%lld\n", a * b);
}
return 0;
}
A. Puzzle From the Future
oj: CodeForces
题意
定义一种运算:给定两个包含前导 0 0 0 的只由 0 0 0 和 1 1 1 构成的整数 a a a 和 b b b,可以求出他们的和 d d d,例如 a = 0110 a=0110 a=0110, b = 1101 b=1101 b=1101, d = a + b = 0110 + 1101 = 1211 d=a+b=0110+1101=1211 d=a+b=0110+1101=1211,然后将 d d d 的相邻的相同元素合到一起,那么 1211 1211 1211 就变成了 121 121 121。
给定整数 b b b,求出 可以求出最大的 d d d 的 a a a。
题解
和一旦被合并了某些元素,数量级就直接变小了,这对大小的影响是最大的,所以求和的时候要避免任何元素的合并。
其次权越大的位对结果的影响越大,所以让权较大的位的数值尽可能大,然后让权小的位避让权大的位使得和不会被合并。
从左到右枚举 b b b:
- 当 b [ i ] b[i] b[i] 为 1 1 1 时
- d [ i − 1 ] d[i-1] d[i−1] 不为 2 2 2 时, a a a 为 1 1 1, d [ i ] d[i] d[i] 为 2 2 2;
- 否则 a a a 为 0 0 0, d [ i ] d[i] d[i] 为 1 1 1。
- 当 b [ i ] b[i] b[i] 为 0 0 0 时
- d [ i − 1 ] d[i-1] d[i−1] 不为 1 1 1 时, a a a 为 1 1 1, d [ i ] d[i] d[i] 为 1 1 1;
- 否则 a a a 为 0 0 0, d [ i ] d[i] d[i] 为 0 0 0。
代码
#include <bits/stdc++.h>
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 100005;
inline int read() {
int x(0), f(1); char ch(getchar());
while (ch<'0' || ch>'9') {
if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0'&&ch <= '9') {
x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
int n;
char s[maxn];
void sol() {
scanf("%s", s);
int tem = -1;
string ans = "";
_for(i, n) {
if(s[i] == '1') {
if(tem != 2) ans += '1', tem = 2;
else ans += '0', tem = 1;
}
else {
if(tem != 1) ans += '1', tem = 1;
else ans += '0', tem = 0;
}
}
cout << ans << "\n";
}
int main() {
int T = read();
_for(i, T) {
n = read();
sol();
}
return 0;
}