题解:P9127 [USACO23FEB] Equal Sum Subarrays G
说在前面
感觉想了三天才做出来的自己跟个人一样……
小建议:把思路写在代码下面是一种非常好的习惯,容易梳理思路,记得多提问。具体怎么做看后面。
题目分析
接下来,我将带领你一步一步,从根开始,找到解题思路。
首先,题目要求改变
其中,
现在思考:这两个序列有什么特点?
题目中提到:初始状态下没有两个和相等的连续子序列。所以,如果序列中不包含
包含两个行不行?也不行。因为如果两个序列都与
得出结论:只有一个序列包含
则问题转化为:找到一个包含
那么我们先求出所有子序列的和:
for (int i = 1; i <= n; i++) { // 前缀和
s[i] = s[i - 1] + a[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
sum[i][j] = s[j] - s[i - 1];
}
}
然后,统计答案。但是直接做时间复杂度大的逆天,光枚举区间就得六重循环。所以,考虑优化。
先把区间信息存进结构体数组:
struct arr {
int l, r, sum;
} b[N];
...
int cnt = 0;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
b[++cnt] = {i, j, s[j] - s[i - 1]};
}
}
随后,我们想一想:最小差值可能出现在什么地方?
是不是很简单?排序后数组相邻的两个数。
所以,按
struct arr {
...
bool operator < (const arr & x) const {
return sum < x.sum;
}
} b[N];
...
sort(b + 1, b + cnt + 1); // 如果不重载小于号要手写比较函数。
那么,枚举相邻的两个序列就行了。
这里给出证明:
假设最小产生在
即
这与
所以假设不成立。
那么我们如何得知区间是否包含
这时候,我们在结构体里存的
不用去枚举
对于每一个
至此,分析结束,时间复杂度
注意事项
这里不用加绝对值,因为是从小到大排序。
完整代码(最下面就是思路)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500 + 5;
int a[N], s[N];
struct arr {
int l, r, sum;
bool operator < (const arr & x) const {
return sum < x.sum;
}
} b[N * N];
int n;
bool check(int l, int r, int x) {
return l <= x && x <= r;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
b[++cnt] = {i, j, s[j] - s[i - 1]};
}
}
sort(b + 1, b + cnt + 1);
for (int i = 1; i <= n; i++) {
int ans = 0x3f3f3f3f3f3f3f3f;
for (int j = 1; j < cnt; j++) {
if (check(b[j].l, b[j].r, i) && !check(b[j + 1].l, b[j + 1].r, i) || !check(b[j].l, b[j].r, i) && check(b[j + 1].l, b[j + 1].r, i)) {
ans = min(ans, b[j + 1].sum - b[j].sum);
}
}
cout << ans << endl;
}
return 0;
}
/*
题目说要求改变 ai 的值,使得有两个不同的连续子序列相等。
那么,这两个子序列有什么特点?
题目说一开始没有两个不同子序列的和相等,说明 ai 的值对序列有影响。
所以两个序列中至少有一个包含 ai?
不对,应该是只有一个。因为两个序列同时加上相同的值,大小关系不变。
所以要求包含 ai 的序列 和 不包含 ai 的序列 的 最小差值。
先求出所有子序列的和:
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
for (int k = i; k <= j; k++) {
sum[i][j] += a[k];
}
}
}
时间复杂度是 O(n ^ 3),会不会炸?
显然不会。题目指出 1 <= N <= 500。
随后来比较。但是直接比较显然会超时。
那我们如何高效的找到不含 ai 和 含 ai 的序列呢?
把序列信息存入结构体中。
struct arr {
int l, r, sum;
} b[N];
.....
int cnt = 0;
for (int i .....n; i++)
for (int j .....n; j++) {
b[++cnt] = {i, j, sum[i][j]};
}
}
哦,不用这么麻烦,可以先预处理前缀和。
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
for (i......)
for (j.......)
b[++cnt] = {i, j, sum[i] - sum[j - 1]};
现在怎么做?
可以排个序,这样相邻的差值一定是最小的。
假设 ai 的存在情况如下:
0 1 0 0 1 1 0 0
怎么找?
看起来只要找相邻的就可以了。
每次枚举相邻两个区间。看如果一边有 ai 一边没有 ai,就计算答案。
如何证明?
可以知道,排序后差值最小一定产生在相邻两个数。假设不产生在 b(i + 1),产生在 b(i + 2):
则我们可知
bi < b(i + 1) < b(i + 2);
b(i + 1) - bi < b(i + 2);
证毕。
但怎么判断有没有ai这个数呢?
原始思路:枚举 l ~ r 看有没有 ai。
但是题目说了没有相同的子序列,也就是数互不相同!!!
所以 一旦 l <= i <= r,就说明他在这个区间内!!!
所以判断相邻两段区间是否一个包含一个不包含,如果是则 ans = min(ans, b[i + 1].sum - b[i].sum);
输出答案即可。
至此,思路完全打开。
*/
说在后面
若思路、代码讲解有误,欢迎提出!