[USACO 2025 January Contest, Bronze] T3 Cow Checkups 题解

Cow Checkups 题解

题目大意:

对于每一个 \(c=0…n\),求出区间 \([l, r]\) 能使得翻转此区间后满足 \(\sum_{i=1}^n (a_i = b_i)\) 的值恰好为 \(c\) 的数量。

解法:

首先,我们定义两个二维数组:

  • \(s_{l, r}\) : 以 \(l\) 为中点 左右长度为 \(r\),即区间 \([l - r, l + r]\) 翻转后的重合数量
  • \(t_{l, r}\) : 以 \(l, l + 1\) 为中点 左右长度为 \(r\),即区间 \([l - r, l + r + 1]\) 翻转后的重合数量

这样方便处理每个奇、偶数长度的区间。
image

为了方便大家理解这两个数组,我来用样例讲解一下。

\(input:\)

7
1 3 2 2 1 3 2
3 2 2 1 2 3 1

第一,\(s_{3, 2}\) 等于?

根据定义,翻转区间 \([3-2,3+2] = [1,5]\)\(a\) 数组成为了:\(\textcolor{red}{1,2,2,3,1},3,2\)
\(b\) 数组重合的位置\(2,3\),故 \(s_{3,2} = 2\)

第二,\(t_{4,1}\) 等于?

根据定义,翻转区间 \([4-1,4+1+1] = [3,6]\)\(a\) 数组成为了:\(1,3,\textcolor{red}{3,1,2,2},2\)
\(b\) 数组重合的位置\(4,5\),故 \(t_{4,1} = 2\)

数据范围是 \(n \le 7500\),那么我们求这两个数组的时间复杂度只能控制在 \(\mathcal{O}(n^2)\)

由此,一个优秀的算法浮现在了我们的脑海中。

首先,初始化:

\(s_{i,0} = (a_i == b_i)\)

\(t_{i,0} = (a_i == b_{i+1}) + (a_{i + 1} == b_i)\)

so easy,就不多说了。

然后,怎么转移?

其实也不难。

\(s_{i,j} = s_{i,j-1} + (a_{i-j} == b_{i+j}) + (a_{i+j} == b_{i-j})\)

相当于 \(s_{i,j}\)\(s_{i,j-1}\) 的基础上左右各增加了一个数,只需再算左右两数翻转后是否满足 \(a_i == b_i\) 即可。

\(t_{i,j}\) 也同理。

\(t_{i,j} = t_{i,j-1} + (a_{i + j + 1} == b_{i - j}) + (a_{i - j} == b_{i + j + 1})\)

最后,就可以统计答案了。

\(a_i==b_i\) 做一个前缀和。

枚举要翻转的区间 \([l,r]\),用 \(s\)\(t\) 数组去计算翻转后的值。

而区间 \([1,l-1]\)\([r+1,n]\) 则用前缀和计算即可。

讲一下怎么实现

求出区间中点 \(mid = (l+r+1) \div 2\)

若区间长度为奇数,答案就是 \(s_{mid, mid-l}\)
若区间长度为偶数,答案就是 \(t_{mid, mid-l}\)

自己想一想,建议画图去理解一下。其实并不难。

Code:

由上面的分析,敲出了以下代码:

#include <bits/stdc++.h>
#define ll long long
#define pii pair <int, int>
using namespace std;
const int N = 7.5e3 + 10;
int a[N], b[N]; ll s[N][N], t[N][N], pre[N], ans[N];
// s[l][r] : 以 l 为中点 左右长度为 r reverse 后的重合数量
// t[l][r] : 以 l, l + 1 为中点 左右长度为 r reverse 后的重合数量
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int n; cin >> n;
	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 <= n; ++i) {
		s[i][0] = (a[i] == b[i]);
		for (int j = 1; j <= min(i - 1, n - i); ++j)
			s[i][j] = s[i][j - 1] + (a[i + j] == b[i - j]) + (a[i - j] == b[i + j]);
	}
	for (int i = 1; i < n; ++i) {
		t[i][0] = (a[i] == b[i + 1]) + (a[i + 1] == b[i]);
		for (int j = 1; j <= min(i - 1, n - i - 1); ++j)
			t[i][j] = t[i][j - 1] + (a[i + j + 1] == b[i - j]) + (a[i - j] == b[i + j + 1]);
	}
	for (int i = 1; i <= n; ++i)
		pre[i] = pre[i - 1] + (a[i] == b[i]);
	for (int l = 1; l <= n; ++l) {
		for (int r = l; r <= n; ++r) {
			int mid = (l + r) / 2;
			int ret = 0;
			if ((r - l + 1) & 1) {
				ret = (s[mid][mid - l]) + pre[l - 1] + pre[n] - pre[r];
			} else {
				ret = (t[mid][mid - l]) + pre[l - 1] + pre[n] - pre[r];
			}
			ans[ret] ++;
		}
	}
	for (int i = 0; i <= n; ++i)
		cout << ans[i] << "\n";
	return 0;
}

样例本地全过,交上去就 \(MLE\) 了,一分没有。

怎么办?难道要前功尽弃吗?不,先卡卡常。

不必要的 \(long\ long\) 去掉。\(s\)\(t\) 数组可以只留一个(即先求长度为奇数的区间,再求偶数区间)。

又写出以下代码:

#include <bits/stdc++.h>
#define ll long long
#define pii pair <int, int>
using namespace std;
const int N = 7.5e3 + 10;
int a[N], b[N];
int s[N][N], pre[N], ans[N];
// s[l][r] : 以 l 为中点 左右长度为 r reverse 后的重合数量
// s2[l][r] : 以 l, l + 1 为中点 左右长度为 r reverse 后的重合数量
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int n; cin >> n;
	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 <= n; ++i) {
		s[i][0] = (a[i] == b[i]);
		for (int j = 1; j <= min(i - 1, n - i); ++j)
			s[i][j] = s[i][j - 1] + (a[i + j] == b[i - j]) + (a[i - j] == b[i + j]);
	}
	for (int i = 1; i <= n; ++i)
		pre[i] = pre[i - 1] + (a[i] == b[i]);
	for (int l = 1; l <= n; ++l) {
		for (int r = l; r <= n; ++r) {
			int mid = (l + r) / 2;
			int ret = 0;
			if ((r - l + 1) & 1) {
				ret = (s[mid][mid - l]) + pre[l - 1] + pre[n] - pre[r];
				ans[ret] ++;
			} else ;
		}
	}
	for (int i = 1; i < n; ++i) {
		s[i][0] = (a[i] == b[i + 1]) + (a[i + 1] == b[i]);
		for (int j = 1; j <= min(i - 1, n - i - 1); ++j)
			s[i][j] = s[i][j - 1] + (a[i + j + 1] == b[i - j]) + (a[i - j] == b[i + j + 1]);
	}
	for (int l = 1; l <= n; ++l) {
		for (int r = l; r <= n; ++r) {
			int mid = (l + r) / 2;
			int ret = 0;
			if ((r - l + 1) & 1) ;
			else {
				ret = (s[mid][mid - l]) + pre[l - 1] + pre[n] - pre[r];
				ans[ret] ++;
			}
		}
	}
	for (int i = 0; i <= n; ++i)
		cout << ans[i] << "\n";
	return 0;
}

不出所料,通过了。

总结:

这是一道很好的铜组题目,我的这种方法稍微需要动脑,不知道是否有更简单的做法。

另外,这题也是新手练题的极佳选择。Good!

posted @ 2025-01-28 19:33  云岚天上飘  阅读(168)  评论(0编辑  收藏  举报

喜欢请打赏

扫描二维码打赏

了解更多