Codeforces Round 1019 (Div. 2)
A. Common Multiple
题意:在\(a\)中选出一个子序列\(x\),然后构造一个长度相等的没有重复数字的数字\(y\),使得任意的\(i, j\)都有\(x_i\times y_i = x_j \times y_j\)。要求子序列最长。
没有重复数字,那么\(y\)里任意两个数和\(x\)任意一个数的乘积是不一样的。也就是说\(x_i\)有一个对于的\(y\)。
那么\(x\)里也不能有相同的数。答案就是\(a\)中不同数字个数。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::ranges::sort(a);
a.erase(std::unique(a.begin(), a.end()), a.end());
std::cout << a.size() << "\n";
}
B. Binary Typewriter
题意:一个长度为\(n\)的二进制串,定义价值为\((s_1 == 1) + n + \sum_{i=1}^{n-1} s_i \ne s_{i+1}\),你可以翻转它的一个字串。求最小价值。
分类讨论了一下,把连续相同的段压缩为一个数字,也就是\(110011\)变成\(101\)。那么如果只有一个字符,答案是\(n + (s_1 == 1)\)。如果有两个字符,那么不管是\(01\)还是\(10\)都可以变成\(01\),答案是\(n\)。否则发现一次翻转使价值减2。输出\((s_1 == 1) + n + \sum_{i=1}^{n-1} s_i \ne s_{i+1} - 2\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
int ans = s[0] == '1';
for (int i = 0; i + 1 < n; ++ i) {
ans += s[i] != s[i + 1];
}
std::string t;
for (int i = 0; i < n; ++ i) {
if (t.empty() || t.back() != s[i]) {
t += s[i];
}
}
if (t.size() == 1) {
std::cout << n + (s[0] == '1') << "\n";
} else if (t.size() <= 3) {
std::cout << n + 1 << "\n";
} else {
int ans = s[0] == '1';
for (int i = 0; i + 1 < n; ++ i) {
ans += s[i] != s[i + 1];
}
std::cout << ans + n - 2 << "\n";
}
}
C. Median Splits
题意:把数组分成三段,使得三段的中位数组成的数组的中位数小于等于\(k\)。
感觉这场\(c,d\)都是乱搞搞过的。
记录\(sum_i\)为前\(i\)个小于等于\(k\)的个数。
然后把满足中位数小于等于\(k\)的前缀和后缀存下来。
如果最前的前缀和最后的后缀中间至少有一个数,就满足条件了。
否则如果前缀个数或后缀个数大于\(2\)也满足条件。再否则讨论两个前缀和两个后缀的情况,如果两个前缀之间中位数小于等于\(k\)也满足,后缀同理讨论。
upd:为什么前缀个数大于2就满足条件?
以前缀为例,取最前面的两个合法前缀,如果第一个前缀长度是偶数,那么它到后一个前缀之间的区间也满足,因为它的小于等于k的数正好有一半,所以后一个前缀能满足条件一定是后面出现的小于等于k的数更多。如果第一个前缀长度是奇数,那么下一个满足条件的前缀一定挨着它且这个前缀长度是偶数(因为第一个前缀是最前的,它是奇数那么肯定是单独一个小于等于k的数,那么前两个数就至少有一个数小于等于k了),如果后面还有满足条件的前缀就和上面讨论的一样。所以只要有三个前缀就一定满足。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<int> sum(n + 1);
for (int i = 0; i < n; ++ i) {
sum[i + 1] = sum[i] + (a[i] <= k);
}
std::vector<int> l, r;
for (int i = 1; i <= n; ++ i) {
if (sum[i] >= (i + 1) / 2) {
l.push_back(i);
}
}
for (int i = 1; i <= n; ++ i) {
if (sum[n] - sum[i - 1] >= (n - i + 2) / 2) {
r.push_back(i);
}
}
if (l.size() && r.size() && l[0] + 1 < r.back()) {
std::cout << "YES\n";
} else {
if (l.size() > 2 || r.size() > 2) {
std::cout << "YES\n";
return;
}
if (l.size() == 2 && (sum[l[1]] - sum[l[0]]) >= (l[1] - l[0] + 1) / 2) {
std::cout << "YES\n";
return;
}
if (r.size() == 2 && (sum[r[1] - 1] - sum[r[0] - 1]) >= (r[1] - r[0] + 1) / 2) {
std::cout << "YES\n";
return;
}
std::cout << "NO\n";
}
}
D. Local Construction
题意:一开始有一个排列,第奇数时刻把所有\(p_i \leq p_{i+1}, p_i \leq p_{i-1}\)的位置保留,其它删去,偶数时刻则是保留\(p_i \geq p_{i+1}, p_i \geq p_{i-1}\)的位置。现在给出每个位置被删掉的时刻,求一个合法的排列。
模拟,按时刻从小到大枚举,把所有这一时刻的数都取出来,分为\(-1\)的左边和右边的两部分。然后如果是奇数时刻左边从大到小给数,右边从小到大给数。偶数时刻左边从小到大,右边从大到小。这样可以保证这些数一定在这一时刻被删掉。
然后最后剩下的一个数给没被删的那个位置。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
std::vector<int> ans(n + 1);
int m = *std::max_element(a.begin(), a.end());
int l = 1, r = n;
for (int i = 1; i <= m; ++ i) {
std::vector<int> b[2];
int x = 0;
for (int j = 1; j <= n; ++ j) {
x |= a[j] == -1;
if (a[j] == i) {
b[x].push_back(j);
}
}
std::ranges::reverse(b[1]);
for (auto & j : b[0]) {
if (i & 1) {
ans[j] = r -- ;
} else {
ans[j] = l ++ ;
}
}
for (auto & j : b[1]) {
if (i & 1) {
ans[j] = r -- ;
} else {
ans[j] = l ++ ;
}
}
}
for (int i = 1; i <= n; ++ i) {
if (ans[i] == 0) {
ans[i] = l;
break;
}
}
for (int i = 1; i <= n; ++ i) {
std::cout << ans[i] << " \n"[i == n];
}
}