[题解] SPOJ GSS1 - Can you answer these queries I

[题解] SPOJ GSS1 - Can you answer these queries I

· 题目大意

要求维护一段长度为 \(n\) 的静态序列的区间最大子段和。
\(m\) 次询问,每次询问输出区间 \([L,R]\) 的最大子段和。
\(|a[i]| \leq 15007\)\(1 \leq m,n\leq5\times10^4\)

· 解题思路

首先想到如果用线段树的方法,那么预处理时间复杂度为\(O(n)\),总询问复杂度为\(O(m\cdot logn)\)
当然这么想在这一题中没问题,但是如\(m\)的范围更大一点呢?比如\(1 \leq m \leq 1 \times10^7\)。这时候如果用线段树,很有可能会\(TLE\)
既然没有修改,那么可以用\(ST\)表或者猫树这种 \(O(1)\) 查询的数据结构来完成这一题,于是笔者选择用猫树来完成这一题。
因为ST表太丑了(doge)
总时间复杂度为\(O(n\cdot logn+m)\)

· 代码实现

注释在代码里

#include <cstdio>
#include <algorithm>
#include <ctype.h>
#define reg register
using namespace std;
namespace io {
template<typename T>inline void read(T &x) {
  char f=0,ch; x = 0;
  while(!isdigit(ch=getchar())) f |= ch == ‘-‘;
  while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
  x = f? -x: x;
}
char ch[20];
template<typename T>inline void write(T x) {
  (x < 0) && (x = -x, putchar(‘-‘));
  x || putchar(‘0‘);
  reg int i=0;
  while(x) ch[i++] = x % 10 ^ 48, x /= 10;
  while(i) putchar(ch[--i]);
}
}//快读快写
#define rd io::read
#define wt io::write
const int maxN = 100020;
struct CatTree {
  int pre, sum;
}t[maxN << 1][22];//维护一下区间前缀/后缀最大值,用于合并,然后维护一下区间子串最大值
int d[maxN << 1], pos[maxN << 1], a[maxN << 1];
int len=2, n, m;
void init(int, int, int);
int query(int, int);
int main() {
  freopen("1.in", "r", stdin);
  freopen("1.out", "w", stdout);
  rd(n);
  while(len < n) len <<= 1;
  for(reg int i = 1; i <= n; ++i) rd(a[i]);
  for(reg int i = 1; i <= len << 1; ++i) d[i] = d[i >> 1] + 1;
  init(1, 1, len);
  rd(m);
  for(reg int i = 1, l, r; i <= m; ++i) {
    rd(l); rd(r);
    printf("%d\n", query(l, r));
  }
  return 0;
}
void init(int k, int l, int r) {
  if (l==r) {pos[l]=k;return;}
  int mid = l + r >> 1, pre , str;
  pre = str = t[mid][d[k]].sum = t[mid][d[k]].pre = a[mid];
  str = max(str, 0);
  for(reg int i = mid - 1; i >= l; --i) {
    pre += a[i], str += a[i];
    t[i][d[k]].sum = max(t[i + 1][d[k]].sum, str);
    t[i][d[k]].pre = max(t[i + 1][d[k]].pre, pre);str = max(str, 0);
  }//处理左边区间子串最大值和左边区间后缀最大值。
  pre = str = t[mid + 1][d[k]].sum = t[mid + 1][d[k]].pre = a[mid + 1];
  str = max(str, 0);
  for(reg int i = mid + 2; i <= r; ++i) {
    pre += a[i], str += a[i];
    t[i][d[k]].sum = max(t[i - 1][d[k]].sum, str);
    t[i][d[k]].pre = max(t[i - 1][d[k]].pre, pre);str = max(str, 0);
  }
  init(k << 1, l, mid);init(k << 1 | 1, mid + 1, r);
}
int query(int l, int r) {
    if (l == r) return a[l];
    int fa = d[pos[l]] - d[pos[l] ^ pos[r]];//找到x和y的LCA
    return max(max(t[l][fa].sum, t[r][fa].sum), t[l][fa].pre + t[r][fa].pre);
}
//最后答案就等于Max{左边的区间子串最大值,右边的区间子串最大值,左边的后缀最大值+右边的前缀最大值}

[题解] SPOJ GSS1 - Can you answer these queries I

上一篇:STM32+果云GA6-GPRS/GSM模块+MQTT+HTTP协议连接中移OneNet上传GPS数据定位


下一篇:ADO.NET 的 DataSet(很重要)