Codeforces Round 1005 (Div. 2)
A. Brogramming Contest
题意:给你一个字符串\(s\),还有一个空字符串\(t\),你每次可以把\(s\)的一个后缀放到\(t\)后面,或者把\(t\)的一个后缀放到\(s\)后面,求使得\(s\)全0\(t\)全1的最小操作数。
考虑该如何操作,如果\(s\)后面是一段零,那么如果它前面有\(1\)则要和前面的全1段一起给\(t\),然后\(t\)再把零给回去,这时这个零段就和前面的零段并在一起了,如果之后还有这个操作,可以看作是前面的那个零段贡献的,那么每个前面有1的零段都会有1贡献。如果后面是1段,那么直接给\(t\)就行了,相当于每个1段都有1个贡献。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
int ans = 1;
for (int i = 1; i < n; ++ i) {
ans += s[i] != s[i - 1];
}
if (s[0] == '0') {
-- ans;
}
std::cout << ans << "\n";
}
B. Variety is Discouraged
题意:一个数组的价值为数组长度减不同元素个数。给你一个数组,求删去一个区间得到价值最大的且长度最短的子数组。
如果删除一个数,它对价值的贡献是小于等于0的,这个数出现一次,则价值不变,出现多次则价值变小。那么应该删去最长的连续的出现一次的数。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<int> cnt(n + 1);
for (int i = 0; i < n; ++ i) {
++ cnt[a[i]];
}
int ans = 0, ansl = 0, ansr = 0;
for (int i = 0; i < n; ++ i) {
if (cnt[a[i]] == 1) {
int j = i;
while (j + 1 < n && cnt[a[j + 1]] == 1) {
++ j;
}
if (j - i + 1 > ans) {
ans = j - i + 1;
ansl = i, ansr = j;
}
i = j;
}
}
if (ans == 0) {
std::cout << 0 << "\n";
} else {
std::cout << ansl + 1 << " " << ansr + 1 << "\n";
}
}
C. Remove the Ends
题意:给你一个数组\(a\),每次选择一个\(i\),如果\(a_i > 0\)则删去\([1, i]\)这个子数组,否则如果\(a_i < 0\)则删去\([i, n]\)这个子数组。价值是所有选择位置上的数的绝对值之和。求最大价值。
可以取到一个前缀的所有正数和一个后缀的所有负数。且没有一个正数会在负数的后面。那么预处理前缀最大值和后缀最大值,枚举最后删除的位置取最大即可。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<i64> pre(n + 1), suf(n + 2);
for (int i = 0; i < n; ++ i) {
pre[i + 1] = pre[i] + (a[i] > 0 ? a[i] : 0);
}
for (int i = n - 1; i >= 0; -- i) {
suf[i + 1] = suf[i + 2] + (a[i] < 0 ? -a[i] : 0);
}
i64 ans = 0;
for (int i = 1; i <= n; ++ i) {
ans = std::max(ans, pre[i] + suf[i]);
}
std::cout << ans << "\n";
}
D. Eating
题意:给你一个数组,\(q\)次询问,每次给一个\(x\)。这个\(x\)从后面开始,如果前面的那个小于等于它,则可以吃掉它,并且\(x = x \oplus a_n\),求每个\(x\)可以吃掉几个。
先把数组翻转,那么变成了从前往后吃,然后记一个前缀异或数组\(pre\)。考虑按位处理。从高位往低位,如果这一位是1,那么可以一直吃到\(a\)中下一个这一位为1的位置前,然后判断能不能吃它,能吃那么这一位因为和异或了变成0,那么对于后面的1肯定吃不到了,记后面的1的位置为\(r\),然后到\(r\)之前的0还是有机会吃的,这个交给下一位去判断,如果不能吃,则把前面的零都吃完就可以break了,因为后面再也不能吃了。如果这一位是9 ,那么类似的记个\(r\)为下一个1的位置。到了下一位,记前面已经吃到了\(l\),如果是\(x \oplus pre_l\)这一位是1,记下一个1位置是\(next\),那么可以一直吃到\(\min(next, r) - 1\)这个位置,如果\(r > next\),那么和上一位一样判断能不能吃到这一位。不能吃就直接把前面的零都吃了就可以break了。然后如果为零也是更新\(r\)。就这些一直遍历所有为答案就是\(r-1\)。
点击查看代码
void solve() {
int n, q;
std::cin >> n >> q;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
std::reverse(a.begin() + 1, a.end());
a.push_back(0);
std::vector<int> pre(n + 2);
for (int i = 1; i <= n + 1; ++ i) {
pre[i] = pre[i - 1] ^ a[i];
}
std::vector next(n + 2, std::array<int, 30>{});
std::fill(next[n + 1].begin(), next[n + 1].end(), n + 1);
for (int i = n; i >= 1; -- i) {
next[i] = next[i + 1];
for (int j = 0; j < 30; ++ j) {
if (a[i] >> j & 1) {
next[i][j] = i;
}
}
}
while (q -- ) {
int x;
std::cin >> x;
int l = 0, r = n + 1;
for (int i = 29; i >= 0; -- i) {
if ((x ^ pre[l]) >> i & 1) {
if (r == next[l + 1][i]) {
break;
}
// std::cout << i << " " << l << " " << r << " " << next[l + 1][i] << "\n";
if (next[l + 1][i] < r && (x ^ pre[next[l + 1][i] - 1]) >= a[next[l + 1][i]]) {
l = next[l + 1][i];
r = std::min(r, next[l + 1][i]);
} else {
r = std::min(r, next[l + 1][i]);
break;
}
} else {
r = std::min(r, std::max(l + 1, next[l + 1][i]));
}
// std::cout << l << " " << r << "\n";
}
std::cout << r - 1 << " \n"[!q];
}
}
E. Mycraft Sand Sort
题意:给你一个排列,和一个颜色数组,在一个矩阵中在每一行会生成对应排列数字个数的方块,然后所有方块都往下落。求有多少排列和颜色的方案可以在所有方块下落后和当前这个的每个位置的方块颜色都一样。
赛时手玩了一下,容易发现谁在谁上面都是固定,所以你颜色数组不能变,但同时观察样例发现,相同颜色的行也许可以交换位置,使得最终矩阵不变。然后我赛时也发现了,两行能够交换,只有它们之间的数都小于这两个数才可以。其实这已经观察的差不多了,但我还是不会写。实在太菜了。我赛时主要是不会怎么维护这个东西然后取计数了,比如有一些数可以交换,但可能也有另一部分可以和当前这些数的一部分可以交换,我就是被这里难住了。实际上,利用两个可交换位置的数它们中间的数都小于它们的性质,我们可以按值从小到大做。然后用并查集维护每个可以交换的联通块,那么对于当前这行,它是所有同颜色里目前最小的,然后它可以交换的行我们都维护出来了,那么方案数就之间乘上联通块大小,然后为了后面产生重复贡献,应该把这个联通块的个数减一,这样我们就在一个联通块里,每次选最小的和其他都匹配,然后删去这个最小的,然后到后面次小的和剩下比它大的匹配,不会和之前这个最小的匹配了,就不会用重复。我们每次删除一个数,因为它是最小的数,那么它的两边的联通块如果颜色相同是可以合并的,于是用链表维护。
点击查看代码
struct DSU {
std::vector<int> fa, cnt;
DSU(int _n) {
init(_n);
}
void init(int _n) {
fa.assign(_n, 0);
cnt.assign(_n, 1);
std::iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return false;
}
fa[y] = x;
cnt[x] += cnt[y];
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
int size(int x) {
return cnt[find(x)];
}
};
void solve() {
int n;
std::cin >> n;
std::vector<std::pair<int, int>> a(n);
for (int i = 0; i < n; ++ i) {
int x;
std::cin >> x;
-- x;
a[i] = {x, i};
}
std::vector<int> c(n);
for (int i = 0; i < n; ++ i) {
std::cin >> c[i];
}
std::sort(a.begin(), a.end());
std::vector<int> l(n), r(n);
for (int i = 0; i < n; ++ i) {
l[i] = i - 1;
r[i] = i + 1;
}
auto del = [&](int p) -> void {
if (l[p] != -1) {
r[l[p]] = r[p];
}
if (r[p] != n) {
l[r[p]] = l[p];
}
};
DSU dsu(n);
for (int i = 1; i < n; ++ i) {
if (c[i] == c[i - 1]) {
dsu.merge(i, i - 1);
}
}
Z ans = 1;
for (int i = 0; i < n; ++ i) {
int p = a[i].second;
ans *= (Z)dsu.size(p);
dsu.cnt[dsu.find(p)] -= 1;
if (l[p] != -1 && r[p] != n && c[l[p]] == c[r[p]]) {
dsu.merge(l[p], r[p]);
}
del(p);
}
std::cout << ans << "\n";
}