Average UVA - 1451 数形结合

本文介绍了一种求解最大平均值子数组的算法,针对给定长度的01序列,寻找长度至少为L的连续子串,使子串数字平均值最大。通过前缀和与下凸包技巧,实现了O(n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

大意:

给定一个长度为n的01串,选一个长度至少为L的连续子串,使得子串中数字的平均值最大。

如果有多解,子串长度应尽量小;如果仍有多解,起点编号尽量小。序列中的字符编号为1~n,因此[1,n]就是完整的字符串。1≤n≤100000,1≤L≤1000。例如,对于如下长度为17的序列00101011011011010,如果L=7,最大平均值为6/8(子序列为[7,14],其长度为8);如果L=5,子序列[7,11]的平均值最大,为4/5。

【分析】
先求前缀和Si=A1+A2+…+Ai(规定S0=0),然后令点Pi=(i, Si),则子序列i~j的平均值为(Sj-Si-1)/(j-i+1),也就是直线P(i-1)Pj的斜率。这样可得到主算法:从小到大枚举t,快速找到t'≤t-L,使得Pt'Pt斜率最大。注意题目中的Ai都是0或1,因此每个Pi和上一个P(i-1)相比,都是x加1,y不变或者加1。

对于给定的t,要找的点Pt'在Pt的左边。假设有3个候选点Pi、Pj、Pk,下标满足i<j<k<t,并且3个点成上凸形状(Pj为上凸点)。假设Pt的x坐标为x0,根据定义,Pt的y坐标一定不小于Pk的y坐标,因此Pt一定位于A、B、C3条线段/射线之一,如图8-16所示。

  1. 当Pt在射线A上时,Pk比Pj好(即PkPt的斜率比PjPt的斜率大,后同)。
  2. 当Pt在线段B上时,Pi比Pj好。
  3. 当Pt在线段C上时,Pi和Pk都比Pj好。

换句话说,只要出现上凸的情况,上凸点一定可以忽略。假设已经有了一些下凸点,现在又加入了一个点,可能会使一些已有的点变为上凸点,这时就应当将这些上凸点删除。由于被删除的点总是原来的下凸点中最右边的若干个连续
点,所以可以用栈来实现,如图8-17所示。

 得到下凸线之后,对于任何一个点Pt来说,最优点Pt'都在切点,如图8-18所示。

如何求切点呢?随着t的增大,斜率也是越来越大,所以每次求出的t'只会增大,不会减小。因此每次增加到斜率变小时停下来即可。时间复杂度为O(n) 

#include<cstdio>
using namespace std;
const int maxn = 100000 + 5;
int n, L;
char s[maxn];
int sum[maxn], p[maxn]; // average of i~j is (sum[j]-sum[i-1])/(j-i+1)
// compare average of x1~x2 and x3~x4
int compare_average(int x1, int x2, int x3, int x4) {
  return (sum[x2]-sum[x1-1]) * (x4-x3+1) - (sum[x4]-sum[x3-1]) * (x2-x1+1);
}

int main() {
  int T;
  scanf("%d", &T);
  while(T--) {
    scanf("%d%d%s", &n, &L, s+1);
    sum[0] = 0;
    for(int i = 1; i <= n; i++) sum[i] = sum[i-1] + s[i] - '0';
    int ansL = 1, ansR = L;
    // p[i..j) is the sequence of candidate start points
    int i = 0, j = 0;
    for (int t = L; t <= n; t++) { // end point
      while (j-i > 1 && compare_average(p[j-2], t-L, p[j-1], t-L) >= 0) j--; // remove concave points
      p[j++] = t-L+1; // new candidate
      while (j-i > 1 && compare_average(p[i], t, p[i+1], t) <= 0) i++; // update tangent point
      // compare and update solution
      int c = compare_average(p[i], t, ansL, ansR);
      if (c > 0 || c == 0 && t - p[i] < ansR - ansL) {
        ansL = p[i]; ansR = t;
      }
    }
    printf("%d %d\n", ansL, ansR);
  }
  return 0;
}

 

<think>好的,用户之前遇到了一个错误,错误信息是“type 'vector<int>' does not provide a call operator”,也就是试图调用一个vector对象作为函。现在用户的新请求是:“我是想给vector<int> average放置-1”。看起来用户想要把一个vector<int>类型的变量average初始化为包含-1的值,但可能之前的代码中错误地将average当作函调用了。 首先,我需要理解用户的具体需求。用户提到“放置-1”,可能是指初始化vector的时候填充-1,或者在某个位置插入-1。由于用户之前的错误是调用vector对象作为函,可能他们在尝试调用某个函来设置vector的值,但错误地使用了函调用的语法。 接下来,我需要考虑用户可能的代码错误。例如,用户可能写了类似`average(size(nums), -1)`这样的代码,意图可能是调整vector的大小并用-1填充,但正确的做法应该是使用vector的成员函,比如`resize()`或构造函。例如,`vector<int> average(size, -1);`,其中size是元素量,-1是初始值。 然后,用户可能对C++的vector初始化方法不太熟悉,导致混淆了函调用和对象初始化。需要解释正确的初始化方式,比如直接声明vector时指定大小和初始值,或者使用成员函如assign()或resize()来填充。 还需要检查用户是否有命名冲突的问题,比如之前定义了一个名为average的函,然后又声明了一个同名的vector变量,导致编译器误解。建议用户更改变量名以避免冲突,或者确保在使用vector时正确初始化。 另外,用户可能想返回一个包含-1的vector,但错误地尝试返回函调用的结果。需要说明如何正确构造并返回vector,而不是调用不存在的函。 最后,总结用户的常见错误,提供正确的代码示例,并强调正确使用vector的初始化方法和避免命名冲突的重要性。</think>### 分步解析 --- #### **问题核心** 你想将 `vector<int> average` 初始化为包含 `-1`,但代码中可能错误地尝试通过函调用方式(如 `average(...)`)操作它,导致 `call operator` 错误。 --- ### **1. 错误原因** - **直接问题**: `average` 是 `vector<int>` 类型对象,**不能像函一样被调用**。 错误示例: ```cpp vector<int> average; average(5, -1); // 错误:试图将 vector 当作函调用 ``` 编译器会报错:`type 'vector<int>' does not provide a call operator`。 --- ### **2. 正确初始化 `vector<int> average` 的方法** #### **方法1:声明时直接初始化** 使用构造函直接指定大小和初始值: ```cpp #include <vector> using namespace std; vector<int> average(10, -1); // 创建包含10个-1的vector ``` - **参1**: `size`(元素量) - **参2**: `value`(所有元素的初始值) #### **方法2:后续填充** 通过 `assign()` 或 `resize()` 方法修改内容: ```cpp vector<int> average; average.assign(5, -1); // 填充5个-1(会覆盖原有内容) // 或 average.resize(8, -1); // 调整大小为8,新增元素初始化为-1 ``` #### **方法3:循环填充** 手动遍历并插入元素: ```cpp vector<int> average; for (int i = 0; i < 10; i++) { average.push_back(-1); } ``` --- ### **3. 常见错误场景** #### **场景1:误将 vector 当作函调用** ```cpp vector<int> average; average(5, -1); // 错误:vector 没有 operator() ``` **修正**:改用 `assign()` 或构造函: ```cpp vector<int> average(5, -1); // 正确 ``` #### **场景2:函名与变量名冲突** 若存在同名函 `average` 和对象 `average`,编译器会混淆: ```cpp int average(int a, int b); // 函声明 vector<int> average; // 对象声明 average(5, -1); // 编译器认为你调用的是 vector 对象 → 报错 ``` **修正**:重命名变量或函: ```cpp vector<int> data(5, -1); // 更改变量名 average(5, -1); // 调用函 ``` #### **场景3:未正确理解构造函语法** 尝试通过类似函调用的语法初始化 `vector`: ```cpp vector<int> average = (10, -1); // 错误语法 ``` **修正**:使用圆括号 `()` 初始化: ```cpp vector<int> average(10, -1); // 正确 ``` --- ### **4. 完整代码示例** #### **目标**:创建一个包含 `n` 个 `-1` 的 `vector<int> average`。 **正确代码**: ```cpp #include <vector> using namespace std; int main() { // 方法1:直接初始化 vector<int> average1(5, -1); // 5个-1 // 方法2:后续填充 vector<int> average2; average2.assign(3, -1); // 3个-1 // 方法3:调整大小并填充 vector<int> average3; average3.resize(4, -1); // 4个-1 return 0; } ``` --- ### **5. 关键总结** - **核心错误**:试图将 `vector` 对象当作函调用。 - **正确操作**: 1. 使用构造函 `vector<int> 变量名(size, value)` 直接初始化。 2. 通过 `assign()` 或 `resize()` 方法修改内容。 3. 避免函与变量同名。 - **附加建议**: - 若需动态调整大小,优先使用 `resize()`。 - 若需完全覆盖原有内容,使用 `assign()`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值