题解:P10450 [USACO03MAR] Best Cow Fences G
$ O(n^3) $ 做法
-
第一层循环先跑一遍长度(题目有限制长度不小于 $ L $)。
-
第二层循环跑一遍起点,千万要注意不要越界 QwQ。
-
最后再遍历这个区间求最大值。
附上我丑陋的代码和提交记录,这个代码可以得 42 分。
#include <bits/stdc++.h>
using namespace std;
const int NR = 1e5 + 5;
long long n, l, a[NR], sum, ave;
int main() {
cin >> n >> l;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = l; i <= n; i++) //先枚举长度
for (int j = 1; j <= n - i + 1; j++) { //枚举起点
sum = 0;
for (int k = j; k <= j + i - 1; k++)//遍历,求和
sum += a[k];
ave = max(ave, sum * 1000 / i);
}
cout << ave;
return 0;
}
$ O(n^2) $ 做法
根据上面,我们可以发现求和的那段过程可以用前缀和优化,我们可以先 $ O(n) $ 预处理前缀和,再枚举。
如何预处理前缀和
我们用 \(s_i\) 表示前缀和。
\[s_i = \sum_{j = 1}^i a_j
\]
\(s\) 数组的递推方式如下。
\[s_i = s_{i - 1} + a_i
\]
下面是我丑陋的代码和提交记录,我们已经可以拿到 70 分了。
#include <bits/stdc++.h>
using namespace std;
const int NR = 1e5 + 5;
long long n, l, a[NR], s[NR], sum, ave;
int main() {
cin >> n >> l;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];//前缀和预处理
for (int i = l; i <= n; i++) //同上,先枚举长度
for (int j = 1; j <= n - i + 1; j++) { //枚举起点
sum = s[j + i - 1] - s[j - 1];//求区间和
ave = max(ave, sum / i * 1000);
}
cout << ave;
return 0;
}
$ O(n \log W) $ 做法
我们发现这道题平均数越小越容易达到,符合二分的特点。我们可以找一个数值,再判断是否符合要求。
check 函数该怎么写
这次前缀和与上面有点不同。
- 前缀和:这次前缀和表示前缀和与理想的总和的差。
- 为什么要累求最小值:因为想让平均数越大减的就需要越小。
- 平均数若大于理想的平均数,就返回真,否则返回假。
- 不知道说啥了,送大家一个我用的二分板子。
哦对了,我把 \(s\) 数组的递推式写上
\[s_i = s_{i - 1} + a_i - mid
\]
历尽千辛万苦,我们 AC 了这道题,最后贴上我丑陋的代码和提交记录。
#include <bits/stdc++.h>
using namespace std;
const int NR = 1e5 + 5;
long long n, L;
double l = 0, r = 2e3, s[NR], a[NR];//s[i]表示前缀和与理想的总和的差
bool check(double mid) {
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i] - mid;
double minn = 0;
for (int i = L; i <= n; i++) {//枚举起点
minn = min(minn, s[i - L]);//累求最小值
if(s[i] - minn >= 0)
return 1;
}
return 0;
}
int main() {
cin >> n >> L;
for (int i = 1; i <= n; i++) cin >> a[i];
while (r - l > 1e-5) {
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
cout << int(r * 1000);
return 0;
}
题解结束了,有问题欢迎大家私信我。