CodeTON Round 7 (Div. 1 + Div. 2, Rated, Prizes!)

基本情况

A题花了快半小时,做出来了但是不如正解。

B题又是老毛病,一条路走到黑,爆搜打出来超时就死命想剪枝和记忆化,没想过换方法(觉得贪心不可行)。

C题其实想的是对的,但是没继续想下去。

A - Jagged Swaps

Problem - A - Codeforces

我的解法

没啥好说的,纯模拟。看到 \(n \leq 10\) 知道能过。

但是细节上出了一些问题导致快半小时才做出来。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int a[15];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;
		bool is_right = true, is_wrong = false;
		for (int i = 1 ; i <= n; i++)
		{
			cin >> a[i];
			if (a[i] < a[i - 1])
			{
				is_right = false;
			}
			if (a[i] == a[i - 1])
			{
				is_wrong = true;
			}
		}
		if (is_right)
		{
			cout << "YES" << endl;
			continue;
		}
		if (is_wrong)
		{
			cout << "NO" << endl;
			continue;
		}
		bool toDo = false;
		bool is_solve = true;
		do
		{
			is_solve = true;
			toDo = false;
			for (int i = 2; i <= n - 1; i++)
			{
				if (a[i] > a[i + 1])
				{
					if (a[i] > a[i - 1])
					{
						toDo = true;
						swap(a[i], a[i + 1]);
					}
				}
			}

			for (int i = 2; i <= n; i++)
			{
				if (a[i] < a[i - 1] || a[i] == a[i - 1])
				{
					is_solve = false;
				}
			}
		}
		while (toDo);
		if (!is_solve)
		{
			cout << "NO" << endl;
		}
		else
		{
			cout << "YES" << endl;
		}
	}

	return 0;
}

更优解法

Observe that since we are only allowed to choose \(i \ge 2\) to swap \(a_i\) and \(a_{i+1}\), it means that \(a_1\) cannot be modified by the operation. Hence, \(a_1 = 1\) must hold. In fact, we can prove that as long as \(a_1 = 1\), we will be able to sort the array.

Consider the largest element of the array. Let its index be \(i\). Our objective is to move \(a_i\) to the end of the array. If \(i=n\), it means that the largest element is already at the end. Otherwise, since \(a_i\) is the largest element, this means that $a_{i−1}<a_i $ and \(a_i > a_{i + 1}\). Hence, we can do an operation on index \(i\) and move the largest element one step closer to the end.

We repeatedly do the operation until we finally move the largest element to the end of the array. Then, we can pretend that the largest element does not exist and do the same algorithm for the prefix of size \(n−1\). Hence, we will able to sort the array by doing this repeatedly.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector <ll> vi;
int main() {
    int t;
    cin >> t;
    while (t --> 0) {
        int n;
        cin >> n;
        vi arr(n);
        for (int i = 0; i < n; i++) {
            cin >> arr[i];
        }
        if (arr[0] == 1) {
            cout << "YES";
        } else {
            cout << "NO";
        }
        cout << '\n';
    }
}

B - AB Flipping

Problem - B - Codeforces

我的解法,搜索加剪枝,疯狂TLE。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

#define INF 0x7fffffffffffffff
#define re register

using namespace std;
using ll = long long;

inline void Swap(int &a, int &b)
{
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	return;
}

const int N = 2e5 + 10;

int n;
int a[N];
bool vis[N];
ll ans = -INF;

inline bool check(int a, int b)
{
	return a == 65 && b == 66;
}

inline void dfs(ll now)
{
	if (now < ans)
	{
		return;
	}
	ans = now;
	for (re int i = 1; i <= n - 1; i++)
	{
		if (check(a[i], a[i + 1]) && !vis[i])
		{
			Swap(a[i], a[i + 1]);
			vis[i] = true;
			dfs(now + 1);
			Swap(a[i], a[i + 1]);
			vis[i] = false;
		}
	}
}

int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d", &n);
		ans = -INF;
		char ch;
		getchar();
		for (re int i = 1; i <= n; i++)
		{
			ch = getchar();
			a[i] = ch;
		}
		dfs(0);
		ans = (ans == -INF) ? 0 : ans;
		printf("%lld\n", ans);
	}
	return 0;
}

正确解法

CF标程

If the string consists of only \(\texttt{A}\) or only \(\texttt{B}\), no operations can be done and hence the answer is \(0\).

Otherwise, let \(x\) be the smallest index where \(s_x=\texttt{A}\) and \(y\) be the largest index where \(s_y=\texttt{B}\)

If \(x>y\), this means that the string is of the form \(s=\texttt{B}\ldots\texttt{BA}\ldots\texttt{A}\) Since all the \(\texttt{B}\) are before the \(\texttt{A}\), no operation can be done and hence the answer is also \(0\).

Now, we are left with the case where \(x<y\). Note that \(s[1,x-1] = \texttt{B}\ldots\texttt{B}\) and \(s[y+1,n] = \texttt{A}\ldots\texttt{A}\) by definition. Since the operation moves \(\texttt{A}\) to the right and \(\texttt{B}\) to the left, this means that \(s[1,x - 1]\) will always consist of all \(\texttt{B}\) and \(s[y + 1, n]\) will always consist of all \(\texttt{A}\). Hence, no operation can be done from index \(1\) to \(x−1\) as well as from index \(y\) to \(n−1\).

The remaining indices where an operation could possibly be done are from \(x\) to \(y−1\). In fact, it can be proven that all \(y−x\) operations can be done if their order is chosen optimally.

Let array \(b\) store the indices of \(s\) between \(x\) and \(y\) that contain \(\texttt{B}\) in increasing order. In other words, \(x < b_1 < b_2 < \ldots < b_k = y\) and \(b_i = \texttt{B}\), where \(k\) is the number of occurences of \(\texttt{B}\) between \(x\) and \(y\). For convenience, we let \(b_0=x\). Then, we do the operations in the following order:

\[b_1 - 1, b_1 - 2, \ldots, b_0 + 1, b_0, \]

\[b_2 - 1, b_2 - 2, \ldots, b_1 + 1, b_1, \]

\[b_3 - 1, b_3 - 2, \ldots, b_2 + 1, b_2, \]

\[\vdots \]

\[b_k - 1, b_k - 2, \ldots, b_{k - 1} + 1, b_k \]

It can be seen that the above ordering does operation on all indices between \(x\) and \(y−1\). To see why all of the operations are valid, we look at each row separately. Each row starts with \(b_i−1\), which is valid as \(s_{b_i} = \texttt{B}\) and \(s_{b_i - 1} = \texttt{A}\) (assuming that it is not the last operation of the row). Then, the following operations in the same row move \(\texttt{A}\) to the left until position \(b_{i - 1}\). To see why the last operation of the row is valid as well, even though \(s_{b_{i - 1}}\) might be equal to \(\texttt{B}\) initially by definition, either \(i=1\) which means that \(s_{b_0} = s_x = \texttt{A}\), or an operation was done on index \(b_{i - 1} - 1\) in the previous row which moved \(\texttt{A}\) to index \(b_{i - 1} - 1\). Hence, all operations are valid.

#include <bits/stdc++.h>
using namespace std;
 
char s[200005];
 
signed main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int tc, n; cin >> tc;
	while (tc--) {
		cin >> n; s[n + 1] = 'C';
		for (int i = 1; i <= n; ++i) cin >> s[i];
		int pt1 = 1, pt2 = 1, ans = 0;
		while (s[pt1] == 'B') ++pt1, ++pt2;
		while (pt1 <= n) {
			int cntA = 0, cntB = 0;
			while (s[pt2] == 'A') ++pt2, ++cntA;
			while (s[pt2] == 'B') ++pt2, ++cntB;
			if (s[pt2 - 1] == 'B') ans += pt2 - pt1 - 1;
			if (cntB) pt1 = pt2 - 1;
			else break;
		}
		cout << ans << '\n';
	}
}

某佬写法

首先,在前面出现的 \(\texttt{B}\) 和在后面出现的 \(\texttt{A}\) 肯定没用。

设第一个 \(\texttt{A}\) 出现的下标为 \(l\),第一个 \(\texttt{B}\) 出现的下标为 \(r\),那么对于 \([l,r)\) 之间的每个 \(i\),都能进行一次操作。

证明:

首先我们考虑一个前面是 \(\texttt{A}\) 后面是 \(\texttt{B}\)\(s\)。众所周知,\(s\) 能进行 \(|s|-1\) 次操作。

而我们就可以把 \([l,r]\) 分割成 \(x\) 个上述的串,则根据上述结论,可以进行 \(r-l-x\) 次操作。

我们继续考虑哪儿能进行操作:

我们发现,每个上述串执行完操作后第一项必为 \(\texttt{B}\) 最后一项必为 \(\texttt{A}\),所以在两个串的“接口”还能进行一次操作,也就是说,在 \([l,r]\) 中还能进行 \(x-1\) 次操作作,总共就能进行 \(r-l-x+x-1=r-l-1\) 次操作。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 300005;

char s[N];
int main() {
    int _;
    scanf("%d", &_);
    while (_--) {
        int n;
        scanf("%d%s", &n, s + 1);
        int ans = 0, x = 0, y = 0;
        for (int i = 1; i <= n; i++) {
            if (s[i] == 'B')
                y = i;
            else {
                if (x == 0) x = i;
            }
        }
        if (x != 0 && y != 0) {
            if (y >= x) ans = y - x;
        }
        printf("%d\n", ans);
    }
    return 0;
}

C-Matching Arrays

Problem - C - Codeforces

我的想法

\(a\)\(b\) 序列先排序,然后用贪心。

但是当时主要是觉得解完之后再恢复原序很麻烦,又觉得B题可解,所以没有继续写。

推进正解

贪心其实很明显,拿 \(a\) 的第 \(k\) 大来比 \(b\) 的第 \(k\) 小,只要都满足,就能满足 \(a\) 序列中 \(m\) 个数比 \(b\) 大。

但是还要反向论证,必须除了这 \(m\) 个数以外,其他 \(n - m\) 个数都是序列 \(a\) 小于等于序列 \(b\)

恢复原序问题的方法非常巧妙。

首先初始化一个 \(c\) 数组,内容即 \(a\) 的下标,显然一开始是 \(1\sim n\)

iota(c + 1, c + n + 1, 1);\\快速赋值1~n

然后直接对 \(c\) 数组排序,排序的比较方式是 \(a\) 数组的由小到大。

效果上等于说存储了排序后的 \(a\) 数组的排序前的下标。

然后后面用的时候直接通过 \(c\) 来下标调用 \(a\) 即可。

这样操作就可以在答案数组上直接通过 \(c\) 来定位 \(b\) 应该移动到的位置。

int a[N], b[N], c[N];
int ans[N];
int n, m;

void solve()
{
	std::cin >> n >> m;
	for (int i = 1; i <= n; i++) std::cin >> a[i];
	for (int i = 1; i <= n; i++) std::cin >> b[i];
	std::iota(c + 1, c + n + 1, 1);
	std::sort(c + 1, c + n + 1, [](int p1, int p2){
		return a[p1] < a[p2];
	});
	std::sort(b + 1, b + n + 1);
	for (int i = 1; i <= m; i++)
	{
		int x = c[n - m + i];
		if (a[x] <= b[i]) {std::cout << "NO\n"; return ;}
		ans[x] = b[i];
	}
	for (int i = 1; i <= n - m; i++)
	{
		int x = c[i];
		if (a[x] > b[i + m]) {std::cout << "NO\n"; return ;}	
		ans[x] = b[i + m];
	} 
	std::cout << "YES\n";
	for (int i = 1; i <= n; i++) std::cout << ans[i] << " ";
	std::cout << std::endl;
}
posted @ 2023-11-26 09:56  加固文明幻景  阅读(124)  评论(0编辑  收藏  举报