1.题目描述:点击打开链接
2.解题思路:本题利用线段树解决。事先构造一棵线段树,在每个线段树的结点中维护三个成员变量:max_sub,max_prefix,max_suffix。设此时线段树结点为x,左右端点分别为l,r
- max_sub:区间连续最大和
- max_prefix:区间前缀最大和
- max_suffix:区间后缀最大和
为了同时知道上述三个最大和的左右端点,令它们都是一个结构体segment,成员变量是l,r,val(左端点l,右端点r,最大和val)。那么现在问题的关键是如何查找给定区间[a,b]中这个max_sub。
利用分治法的思想:假设目前为结点u,左右端点分别是L,R,中点是M。那么分三种情况:(1)起点和终点都在左子结点u*2中,那么max_sub(a,b)就是左子结点中的max_sub。
(2)起点和终点都在右子结点u*2+1中,那么max_sub(a,b)就是右子结点中的max_sub。(3)起点在左子结点中,终点在右子结点中,那么max_sub(a,b)就是左子结点的max_suffix和右子结点的max_prefix之和(注意要重载segment结构体中的’+‘)。最后取这三种情况的最大者即可(注意要重载’<‘)。
同理也可以像上述分析那样递推得到max_prefix,max_suffix,具体过程见代码注释。整个过程中建树的时间复杂度是O(N),查询的时间复杂度是O(logN)。
3.代码:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<algorithm> #include<string> #include<sstream> #include<set> #include<vector> #include<stack> #include<map> #include<queue> #include<deque> #include<cstdlib> #include<cstdio> #include<cstring> #include<cmath> #include<ctime> #include<functional> using namespace std; #define N 500000+10 #define INF 100000000 #define lson(x) ((x)<<1)//左子结点编号 #define rson(x) (((x)<<1)+1)//右子结点编号 typedef long long LL; struct segment { int l, r;//左右端点 LL val; segment(int l = 0, int r = 0, LL val = 0){ this->set(l, r, val); } void set(int l, int r, LL val) { this->l = l; this->r = r; this->val = val; } segment operator +(const segment&u)//重载加号,获得合并后的区间及区间连续和 { return segment(min(l, u.l), max(r, u.r), val + u.val); } bool operator <(const segment&u)const//由于下面要取最大值,因此要重载小于号 { if (val != u.val)return val < u.val;//用max函数时取以下三种情况右边的值 if (l != u.l)return l>u.l;//优先让左端点最小 return r>u.r; } }; struct Node { int l, r; segment max_sub, max_prefix, max_suffix; }node[N*4]; int n, m; LL arr[N], s[N]; Node seg_push(Node a, Node b)//维护满足题意的区间 { Node ret; LL suml = s[a.r] - s[a.l - 1];//a结点的连续和 LL sumr = s[b.r] - s[b.l - 1];//b结点的连续和 ret.l = a.l; ret.r = b.r; ret.max_sub = max(a.max_suffix + b.max_prefix, max(a.max_sub, b.max_sub));//取三种情况的最大值:(1)起点在结点a,终点在结点b中;(2)起点和终点都在结点a中;(3)起点和终点都在结点b中; ret.max_prefix = max(a.max_prefix, segment(a.l, a.r, suml) + b.max_prefix);//取两种情况的最大值:(1)终点仍在结点a中;(2)终点在结点b中 ret.max_suffix = max(b.max_suffix, a.max_suffix + segment(b.l, b.r, sumr));//取两种情况的最大值:(1)起点仍在结点b中;(2)起点在结点a中 return ret; } void build_segtree(int root, int l, int r) { if (l == r)//叶子结点 { node[root].l = node[root].r = l; node[root].max_sub.set(l, r, (LL)arr[l]); node[root].max_prefix.set(l, r, (LL)arr[l]); node[root].max_suffix.set(l, r, (LL)arr[l]); return; } int mid = (l + r) / 2; build_segtree(lson(root), l, mid);//递归建树 build_segtree(rson(root), mid + 1, r); node[root] = seg_push(node[lson(root)], node[rson(root)]);//node[root]为满足题意的区间 } Node query(int root, int l, int r) { if (l <= node[root].l&&node[root].r <= r)//待查询区间完全包含了编号为root的线段 return node[root]; int mid = (node[root].l + node[root].r) / 2; if (l <= mid&&r > mid)//起点在mid左侧,终点在mid右侧 return seg_push(query(lson(root), l, r), query(rson(root), l, r)); else if (r <= mid)//起点和终点都在mid左侧 return query(lson(root), l, r); else//起点和终点都在mid右侧 return query(rson(root), l, r); } int main() { freopen("t.txt", "r", stdin); int rnd = 1; while (~scanf("%d%d", &n, &m)) { s[0] = 0; for (int i = 1; i <= n; i++) { scanf("%lld", &arr[i]); s[i] = s[i - 1] + arr[i];//s[i]计算前i个的和 } build_segtree(1, 1, n);//建立线段树,根结点编号为1 printf("Case %d:\n", rnd++); int l, r; while (m--) { scanf("%d%d", &l, &r); Node u = query(1, l, r);//从根结点开始查询线段[l,r],返回满足题意的区间 printf("%d %d\n", u.max_sub.l, u.max_sub.r); } } return 0; }
时间: 2024-11-08 14:38:28