A. MEX Destruction
题意:给你一个数组,每次操作选择一个区间使这个区间变为区间mex,问最少操作使得数组全为0.
容易发现,对任意一个区间,最多两次操作这个区间就会全变成0,于是我们想尽可能操作大的区间。
但并不是直接操作整个数组一定更好,如果我们选择的区间里没有0,那么只需要一次操作,于是判断除去两端连续之后,中间的数里面有没有0,有0则操作两次,没有操作1次。特别的,全0数组不需要操作。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
int zero = 0;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int l = 0, r = n - 1;
while (l < n && a[l] == 0) {
++ l;
}
while (r >= 0 && a[r] == 0) {
-- r;
}
for (int i = l; i <= r; ++ i) {
zero += a[i] == 0;
}
if (l > r) {
std::cout << 0 << "\n";
} else {
std::cout << (zero > 0 ? 2 : 1) << "\n";
}
}
B. pspspsps
题意:给你一个长度为n只由'p', 's', '.'构成的字符串,判断有没有一个长度为n的排列a满足下列条件:
- 每个是p的位置i,都有a1~ai是一个排列。
- 每个是s的位置i,都有ai~an是一个排列。
是点的位置没有要求。
由于整个数组是一个排列,那么任何一个p不能出现在任何一个s的前面,因为每个数只有一个。
对于任意一对i和j(i < j),满足si是s,sj是p,那么他们可以共享j - i + 1个数字,那么i和j之中至少有一个他的需要的排列长度小于等于j - i + 1,否则就是NO,双层循环枚举即可。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
for (int i = 0; i < n; ++ i) {
for (int j = i + 1; j < n; ++ j) {
if (s[i] == 'p' && s[j] == 's') {
std::cout << "NO\n";
return;
} else if (s[i] == 's' && s[j] == 'p' && j + 1 > j - i + 1 && n - i > j - i + 1) {
std::cout << "NO\n";
return;
}
}
}
std::cout << "YES\n";
}
C. MEX Cycle
题意:一个长度为n的数组a要满足他和他的所有“朋友"的mex等于ai,数组是第一个元素和最后一个元素相邻,每个元素的朋友为他的左右邻居。
题目会多给出一组朋友关系。
让你构造一个满足条件的长度为n的数组。
这题差不多写了应该小时,人麻了。
我的写法感觉有点麻烦了,看官方题解就几行代码。
我的解法是第x个为1,第y个为2,这样他们两边应该都是0,考虑x往右走距离y还有几个没填,如果是奇数个,则直接10101...填满,如果是偶数个则x+2位置填2,然后依旧10101...
考虑特殊情况,中间没有数,并且第x+1个和第y-1个相邻,则可以让第x+1个为2,这样就是...012020...,如果他们是一个位置,则不用管,然后同样道理考虑x往左走。特殊情况让y+1填1.
点击查看代码
void solve() {
int n, x, y;
std::cin >> n >> x >> y;
-- x, -- y;
std::vector<int> ans(n);
if (std::abs(x - y) == 1 || std::abs(x - y) == n - 1) {
if (n & 1) {
ans[0] = 2;
for (int i = 1; i < n; ++ i) {
ans[i] = i & 1;
}
} else {
for (int i = 0; i < n; ++ i) {
ans[i] = i & 1;
}
}
} else {
ans[x] = 1; ans[y] = 2;
int len1 = y - 1 - (x + 1) - 1;
if (len1 == 0) {
ans[(x + 1) % n] = 2;
} else if (len1 > 0 && len1 % 2 == 0) {
ans[x + 2] = 2;
for (int i = x + 3; i < y - 1; ++ i) {
ans[i] = i - (x + 3) + 1 & 1;
}
} else if (len1 > 0 && len1 % 2 == 1) {
for (int i = x + 2; i < y - 1; ++ i) {
ans[i] = i - (x + 2) + 1 & 1;
}
}
int len2 = -1;
int l = (x - 1 + n) % n, r = (y + 1) % n;
while (l != r) {
++ len2;
l = (l - 1 + n) % n;
}
if (len2 == 0) {
ans[(y + 1) % n] = 1;
} else if (len2 > 0 && len2 % 2 == 0) {
l = (x - 2 + n) % n;
ans[l] = 2;
int k = 1;
for (int i = (l - 1 + n) % n; i != r; i = (i - 1 + n) % n, k ^= 1) {
ans[i] = k;
}
} else if (len2 > 0 && len2 % 2 == 1) {
l = (x - 2 + n) % n;
int k = 1;
for (int i = l; i != r; i = (i - 1 + n) % n, k ^= 1) {
ans[i] = k;
}
}
}
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] << " \n"[i == n - 1];
}
}
D. Shift + Esc
题意:一个nm的数组,你要从(1, 1)走到(n, m),只能往下或往右,但在走之前,对每个行你可以左移任意次,最后花费为k左移次数+路径值总和。
是个经典dp加了个状态,还是挺好想的。
我们套用f[i][j]的定义,发现无法处理左移情况,怎么办? 发现n和m才200,还可以再开一维,正好补上这个状态!
f[i][j][cnt]表示走到(i, j)且第i行左移了cnt次的最小花费,那么对于同一行可以有f[i][j - 1][cnt]转移过来,但对于上一行,他的任意左移次数状态都可以转移过来,我们可以存个min[i][j],表示(i, j)这个位置滚了任意次中的最小值,那么可以由min[i - 1][j]转移过来。
第三维最多开m - 1,因为多转就转回来了。
点击查看代码
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector a(n, std::vector<i64>(m));
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
std::cin >> a[i][j];
}
}
std::vector f(n + 1, std::vector(m + 1, std::vector<i64>(m, 1e18)));
std::vector min(n + 1, std::vector<i64>(m + 1, 1e18));
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
for (int cnt = 0; cnt < m; ++ cnt) {
i64 w = (i64)k * cnt;
if (i == 1 && j == 1) {
f[i][j][cnt] = a[0][cnt];
}
if (i > 1) {
f[i][j][cnt] = std::min(f[i][j][cnt], min[i - 1][j] + a[i - 1][(j - 1 + cnt) % m]);
}
if (j > 1) {
f[i][j][cnt] = std::min(f[i][j][cnt], f[i][j - 1][cnt] + a[i - 1][(j - 1 + cnt) % m]);
}
min[i][j] = std::min(min[i][j], f[i][j][cnt] + w);
}
// std::cout << min[i][j] << " \n"[j == m];
}
}
std::cout << min[n][m] << "\n";
}
E. Broken Queries
赛后补的
题意:有一个长度为2的幂的01字符串,和一个k,这个字符串只有一个1。每次你可以询问一个区间,假设这个区间和为s,那么当区间长度小于k时,返回s,否则返回1-s。
我们可以通过询问(1, n / 4) 和 (n / 4 + 1, n / 2)来获取1所在的区间位置,因为如果1在(1, n / 2),不管k怎么样,这两个回答一定时不同的,否则1在(n / 2 + 1, n)。
确定1的位置就可以确定k的大小,假设1在左边,那么先询问(1, n / 2), 如果k > n / 2, 那么就会得到0,我们可以二分n / 2 + 1到n的长度,看(1, mid)的值来找第一个回到0的位置,因为他一定是000...1111这样的,满足二段性。
如果k <= n / 2, 一样的二分,只不过我们要利用另一半,因为我们知道1不在另一半,他的区间和都是0,所有可以求(mid, n)来找最后一个1的位置。
点击查看代码
int ask(int l, int r) {
std::cout << "? " << l << " " << r << std::endl;
int res;
std::cin >> res;
return res;
}
void solve() {
int n;
std::cin >> n;
if (ask(1, n / 4) != ask(n / 4 + 1, n / 2)) {
if (ask(1, n / 2)) {
int l = n / 2 + 1, r = n;
while (l < r) {
int mid = l + r >> 1;
if (ask(1, mid) == 0) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << "! " << l << std::endl;
} else {
int l = 1, r = n / 2;
while (l < r) {
int mid = l + r >> 1;
if (ask(n / 2 + 1, n / 2 + mid)) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << "! " << l << std::endl;
}
} else {
if (ask(n / 2 + 1, n)) {
int l = n / 2 + 1, r = n;
while (l < r) {
int mid = l + r >> 1;
if (ask(n - mid + 1, n) == 0) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << "! " << l << std::endl;
} else {
int l = 1, r = n / 2;
while (l < r) {
int mid = l + r >> 1;
if (ask(1, mid)) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << "! " << l << std::endl;
}
}
}