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;
}
posted @ 2023-09-07 15:26  jackle  阅读(6)  评论(0编辑  收藏  举报