Codeforces Round 699 (Div. 2)
Codeforces Round 699 (Div. 2)
A. Space Navigation
题意
给你一个字符串 \(S\)(只包含 \(U,D,L,R\)) ,以及一个目的地坐标 \((p_x, p_y)\),你可以选择一些子序列
按顺序执行他们,问你能不能从 \((0,0)\) 到达 \((p_x,p_y)\)。
数据范围
\(1\leq |S| \leq 10^5\),\(-10^5\leq p_x,p_y\leq 10^5\)。
解题思路
显然我们只要选择那些对我最终坐标有直接影响的操作即可,我们只要从 \((p_x,p_y)\) 反向走即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
void solve() {
int px, py;
cin >> px >> py;
string s;
cin >> s;
for (auto c : s) {
if (c == 'U') {
if (py > 0) py--;
} else if (c == 'D') {
if (py < 0) py++;
} else if (c == 'R') {
if (px > 0) px--;
} else {
if (px < 0) px++;
}
}
cout << (!px && !py ? "YES" : "NO") << "\n";
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
B. New Colony
题意
给你 \(n\) 座山,有 \(k\) 个人按顺序翻越山,直到他们一个人停下或者翻越完所有山,下一个人继续,其规则如下:
- 若 \(h_i\ge h_{i+1}\) ,则他可以翻越第 \(i\) 座山。
- 若 \(h_i<h_{i+1}\),则他停在 \(i\) 这座山,并让 \(h_i=h_i+1\)。
现在问你第 \(k\) 个人会停在哪座山,如果他翻过了所有山,输出 \(-1\)。
数据范围
\(1\leq n,h_i \leq100,1\leq k\leq 10^9\)。
解题思路
首先我们看到 \(k\) 的范围,可能无法做,因为太大了,我们不可能一个个人去模拟,但我们发现
\(n,h\) 的范围都很小,\(h\) 最多是 \(100\),这意味着,当一个山的高度加到了 \(100\),则人一定能翻越,故其实
我们只要模拟最多 \(n\times 100\) 次,后面的人一定就可以翻越所有山了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
void solve() {
int n, k;
cin >> n >> k;
vector<int> h(n);
for (int i = 0; i < n; i++) {
cin >> h[i];
}
int pos;
while (k--) {
pos = -1;
for (int i = 0; i + 1 < n; i++) {
if (h[i] < h[i + 1]) {
h[i]++;
pos = i + 1;
break;
}
}
if (pos == -1) break;
}
cout << pos << "\n";
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
C. Fence Painting
题意
给你两个长度为 \(n\) 的数组 \(a,b\),现在你有一个长度为 \(m\) 的数组 \(c\),你可以用数组 \(c\) 进行染色,注意你染色
的时间顺序就是 \(1\leq i\leq m\),即先染色的位置,有可能会被后面染的颜色覆盖。
你可以令任意的 \(a_i=c_j\),要求每一个 \(c_j\) 都要恰好染一个 \(a_i\),请你构造一种染色方案使得最终数组 \(a=b\)。
如果无解,请你输出 NO。
数据范围
\(1\leq n,m\leq 10^5\),\(1\leq a_i,b_i,c_i\leq n\)。
解题思路
首先我们发现一个关键点,就是染色有时间顺序,我们后来的颜色可以覆盖前面染的。首先我们对于所有
满足 \(a_i\not = b_i\) 的元素,我们先让颜色是 \(b_i\) 的那些 \(c_j\) ,选择时间最靠后的那个染(如果途中这样的 \(c\) 不够了,显然就是无解);接下来我们在从满足 \(a_i=b_i\) 的元素,同理进行染色。那么剩下那些还未处理的,我们只要将他们分配到所有比他染色时间大的点即可,因为这些染色操作是无效的,我们可以最后用一个有效的染色直接覆盖了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
void solve() {
int n, m;
cin >> n >> m;
vector<int> a(n + 1), b(n + 1), c(m + 1);
vector<vector<int>> color(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
for (int i = 1; i <= m; i++) {
cin >> c[i];
color[c[i]].push_back(i);
}
vector<int> ans(m + 1);
vector<int> tmp;
for (int i = 1; i <= n; i++) {
if (a[i] != b[i]) {
if (color[b[i]].size() == 0) {
cout << "NO\n";
return ;
} else {
int t = color[b[i]].back();
ans[t] = i;
color[b[i]].pop_back();
tmp.push_back(t);
}
}
}
for (int i = 1; i <= n; i++) {
if (a[i] == b[i]) {
if (color[b[i]].size()) {
int t = color[b[i]].back();
ans[t] = i;
color[b[i]].pop_back();
tmp.push_back(t);
}
}
}
vector<int> diff;
for (int i = 1; i <= n; i++) {
while (color[i].size()) {
diff.push_back(color[i].back());
color[i].pop_back();
}
}
sort(diff.begin(), diff.end(), greater<int>());
sort(tmp.begin(), tmp.end());
for (int i = 0; i < tmp.size(); i++) {
while (diff.size() && diff.back() < tmp[i]) {
ans[diff.back()] = ans[tmp[i]];
diff.pop_back();
}
}
if (diff.size()) {
cout << "NO\n";
return ;
}
cout << "YES\n";
for (int i = 1; i <= m; i++) {
cout << ans[i] << " \n"[i == m];
}
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D. AB Graph
题意
给你一个 \(n\) 个点的有向完全图,每条边的边权是 'a' 或 'b'。现在让你找一条长度为 \(m\) 的路径,使得路径构成的
字符串是一个回文串,如果可以给出构造方案,否则输出 NO。
数据范围
\(1\leq n \leq 1000\),\(1\leq m \leq 10^5\)。
解题思路
分类讨论:
-
如果 \(m\) 是奇数,那么我们只要在任意两个点 \(u,v\) 之间来回走,一定可以构造出一个回文串,其字符串只有如下几种\(aaa...\),\(bbb...\),\(ababa...\),\(babab...\),这些显然都是回文串。
-
如果 \(m\) 是偶数,这里就需要我们大胆猜测回文串的形态,而且一般这种构造题,都会存在一种简单特殊的构造方法,我们不可能真的去找这样的路径。如果 \(m\%4\not=0\),我们考虑一下 \(aabb\) 这样的串,那么我们可以选择构造 \(aabbaa...\) 这样的串,这样显然是一个回文串;然后对于 \(m\%4=0\),我们考虑一下 \(abba\) 这样的串,我们可以选择构造 \(abbaabba...\) 这样的串,这显然是一个回文串。所以我们只要找到任意三个点 \(x,y,z\),使得存在这样的路径的串,然后在这 \(3\) 个点来回循环的走,就可以构造出长度为 \(m\) 的回文串了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010;
char s[N][N];
void solve() {
int n, m;
cin >> n >> m;
vector<vector<int>> p(n + 1);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> s[i][j];
if (s[i][j] == 'a') p[i].push_back(j);
}
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (s[i][j] == s[j][i] || m % 2) {
cout << "YES\n";
int x = i, y = j;
cout << x;
while (m--) {
cout << " " << y;
swap(x, y);
}
cout << "\n";
return ;
}
}
}
if (m / 2 % 2) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (s[i][j] == 'a' && p[j].size()) {
int x = i, y = j, z = p[j].back();
cout << "YES\n";
cout << x;
while (m) {
cout << " " << y << " " << z;
swap(x, z);
m -= 2;
}
cout << "\n";
return ;
}
}
}
cout << "NO\n";
} else {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (s[i][j] == 'b' && p[i].size()) {
int x = i, y = j, z = p[i].back();
cout << "YES\n";
cout << x;
while (m) {
cout << " " << y << " " << x;
swap(y, z);
m -= 2;
}
cout << "\n";
return ;
}
}
}
cout << "NO\n";
}
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E. Sorting Books
题意
有 \(n\) 本书,每本书有一个颜色 \(a_i\),从左到右排列,现在你可以进行如下操作任意次:
- 选择任意一本书,将他移动到末尾。
现在问你最少操作几次,可以使得所有同颜色的书都在一起(即同在一个连续的子序列里面)。
数据范围
\(1\leq n \leq 5\times 10^5\),\(1\leq a_i \leq n\)。
解题思路
首先我们知道答案一定不会超过 \(n-1\),我们大不了除了最后一本书不动,其他书都按一定的顺序放末尾,最后一定
能达到目的。对于这类类似于删数的问题,我们可以考虑逆向考虑,我们看看到底最多有几本书可以不移动,即我们找
到一个最长的子序列满足它是可以不动的,那么最少操作次数就是 \(n-len\)。
现在研究一下这样的子序列要满足怎么样的条件,首先我们看一些不合法的情况:\(4...4...5..5..5..5..4..4...6...7\)。
如果我们选择子序列 \([5,5,5,5,4,4,6]\) 这样一定是不行的,因为我们发现在前面还有 \(4\),那么由于这个子序列不能动
我们前面的 \(4\) 一定不能通过操作放到这些 \(4\) 的末尾;但 \([5,5,5,5,4,4]\) 这样是合法的,因为这个子序列是以 \(4\) 结尾的
所以我们可以先把前面的 \(4\) 移动到数组末尾,最后通过移动其他数,一定会让这些连在一起。
通过分析,我们可以得到子序列必须满足如下条件
- 首先子序列一定是由一段段连续一样的数构成的:\([5,5,5,5,4,4,4,3,3...]\)
- 如果值为 \(x\) 的一段在这个子序列里面,他要么必须包含数组的所有 \(x\),要么它必须是这个子序列的结尾一段。
故我们就变成经典的 DP 了,我们设 \(dp_i\) 表示考虑了 \(i\backsim n\) 的所有数,所选择满足条件的子序列长度的最大值。
我们考虑倒序来做,有如下转移:
- 若不选择这个数,则 \(dp_i=dp_{i+1}\)
- 若选择这个数接到前面形成的子序列,且从 \(i\) 到这个数最后一次出现的位置 \(j\) 包含了整个数组的所有 \(x\),则有
\(dp_i=dp_{j+1}+cnt[i\backsim j]\),其中 \(cnt[i\backsim j]\) 表示在数组 \([i,j]\) 这个位置 \(x\) 出现了几次。 - 若选择这个数,单独成一个新的子序列,则有 \(dp_i=cnt[x]\),\(cnt[x]\) 表示到目前为止 \(x\) 出现了几次
综上所有转移取最大值即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
int dp[N], cnt[N], freq[N], a[N], last[N];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
cnt[a[i]]++;
}
for (int i = n; i >= 1; i--) {
dp[i] = dp[i + 1];
freq[a[i]]++;
if (!last[a[i]]) last[a[i]] = i;
if (freq[a[i]] == cnt[a[i]]) {
dp[i] = max(dp[i], dp[last[a[i]] + 1] + freq[a[i]]);
} else {
dp[i] = max(dp[i], freq[a[i]]);
}
}
cout << n - dp[1] << "\n";
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
//cin >> t;
t = 1;
while (t--) {
solve();
}
return 0;
}