GSS系列题解——最大子段和系列
开坑啦!
2019 3/28 以前一直不知道怎么搞最大子段和,如今终于可以学习,其实真的很简单啊。
2019 3/29 树链剖分上最大子段和也OK啦
前置技能:线段树
题目大意:询问区间[l,r]的最大字段和
定义:
struct kkk{
int val; //表示该区间的权值和
int left; //表示该区间的前缀最大和
int right; //表示该区间的后缀最大和
int middle; //表示该区间的最大子段和
kkk(){val=left=right=middle=0;} //清0
}tree[maxn];
大家都应知道,线段树基本原理,那么最大子段和放在线段树上,其实就是两个区间的合并时怎么将区间关系,pushup区间的问题。
下面给出两个区间合并的方式:合并的区间为res
合并保证x是左区间,y是右区间,x,y相邻。
首先是val的合并,很简单,区间x的val+区间y的val
res.val=x.val+y.val;
然后是left的合并,前缀最大和只有两种情况,要么是x区间的前缀最大和,要么是x的权值和+y的前缀最大和。结果是这两种情况的max值。证明:贪心。
那么right的合并也差不多,要么是y区间的后缀最大和,要么是y的权值+x的后缀最大和。
至于middle就分几种情况:
1.x区间的middle
2.y区间的middle
3.x区间的后缀最大和+y区间的前缀最大和
代码实现合并操作:
kkk merge(kkk x,kkk y){
kkk res;
res.val=x.val+y.val;
res.middle=max(
max(x.middle,y.middle),
x.right+y.left
);
res.left=max(x.left,x.val+y.left);
res.right=max(y.right,y.val+x.right);
return res;
}
那么pushup操作即是将左儿子和右儿子合并。
查询操作
我们查询时返回一个区间,这个区间在查询时会合并成我们想要查询的那个区间,那么那个区间的middle就是我们要求的答案。
kkk query(int node,int begin,int end,int x,int y){
if(begin>=x&&end<=y)return tree[node]; //包含该区间,直接返回
int mid=(begin+end)>>1;
kkk Left,Right,res;
if(x<=mid) Left=query(L(node),begin,mid,x,y); //查询左区间
if(y>mid) Right=query(R(node),mid+1,end,x,y); //查询右区间
return merge(Left,Right); //合并成一个区间
}
经过思考,我们会发现Left和Right决不是返回整个区间,而是我们要求的区间在Left或Right区间的部分。所以我们可以直接将Left和Right区间合并。
最后输出的就是查询到区间的middle
printf("%d\n",query(1,1,n,x,y).middle);
下面放完整代码:
#include<bits/stdc++.h>
#define maxn 1000001
#define L(node) (node<<1)
#define R(node) ((node<<1)|1)
using namespace std;
struct kkk{
int val,left,right,middle;
kkk(){val=left=right=middle=0;}
}tree[maxn];
int n,m,x,y,a[maxn];
kkk merge(kkk x,kkk y){
kkk res;
res.val=x.val+y.val;
res.middle=max(max(x.middle,y.middle),x.right+y.left);
res.left=max(x.left,x.val+y.left);
res.right=max(y.right,y.val+x.right);
return res;
}
void build(int node,int begin,int end){
if(begin==end){
tree[node].val=tree[node].left=tree[node].right=tree[node].middle=a[begin];
return ;
}else{
int mid=(begin+end)>>1;
build(L(node),begin,mid);
build(R(node),mid+1,end);
tree[node]=merge(tree[L(node)],tree[R(node)]);
}
}
kkk query(int node,int begin,int end,int x,int y){
if(begin>=x&&end<=y)return tree[node]; //包含该区间,直接返回
int mid=(begin+end)>>1;
if(y<=mid) return query(L(node),begin,mid,x,y);
if(x>mid)return query(R(node),mid+1,end,x,y);
kkk Left,Right;
if(x<=mid) Left=query(L(node),begin,mid,x,y); //查询左区间
if(y>mid) Right=query(R(node),mid+1,end,x,y); //查询右区间
return merge(Left,Right); //合并成一个区间
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
printf("%d\n",query(1,1,n,x,y).middle);
}
}
修改操作
修改操作和普通线段树没有什么区别,求最大子段和只和pushup和查询操作有直接关系,其他的操作几乎一样。当然,这里的修指的是区间赋值,如果是区间加的话可能要更加复杂。
单点修代码:
void update(int node,int begin,int end,int x,int val){
if(begin==end){
tree[node].val=tree[node].left=tree[node].right=tree[node].middle=val;
return ;
}else{
int mid=(begin+end)>>1;
if(x<=mid)update(L(node),begin,mid,x,val);
if(x>mid) update(R(node),mid+1,end,x,val);
pushup(node);
}
}
区间修
其实和线段树也是一模一样的,不信看代码:
void update(int node,int begin,int end,int x,int y,int val){
if(begin>=x&&end<=y){
change(node,begin,end,val);
return ;
}else{
pushdown(node,begin,end);
int mid=(begin+end)>>1;
if(x<=mid)update(L(node),begin,mid,x,y,val);
if(y>mid) update(R(node),mid+1,end,x,y,val);
tree[node]=merge(tree[L(node)],tree[R(node)]);
}
}
容易发现,整体的结构和线段树的区间修是一模一样的,但是赋值变成了change,更新pushdown很迷,下面一起来看一下。
change操作是赋值操作,也就是和线段树一模一样的。
void change(int node,int begin,int end,int val){
tree[node].val=(end-begin+1)*val;
tree[node].left=tree[node].right=tree[node].middle=val;
tree[node].tag=val;tree[node].flag=1;
}
可以发现,这里有个flag,表示的是该区间有没有被打tag标记。我们不可以直接让tag=0来判断标记,因为可能tag是赋值为0的,所以我们要用flag来记录有没有打tag。这是一个小细节。
剩下就是pushdown操作了,就是注意一下用flag来判断就好。其他赋值都可以用change来代替,写起来方便很多。
void pushdown(int node,int begin,int end){
if(tree[node].flag==1){
int mid=(begin+end)>>1;
change(L(node),begin,mid,tree[node].tag);
change(R(node),mid+1,end,tree[node].tag);
tree[node].tag=0;tree[node].flag=0;
}
}
那么区间修的代码就这些了,直接调用就可以了。
区间修代码:
void change(int node,int begin,int end,int val){
tree[node].val=(end-begin+1)*val;
tree[node].left=tree[node].right=tree[node].middle=val;
tree[node].tag=val;tree[node].flag=1;
}
void pushdown(int node,int begin,int end){
if(tree[node].flag==1){
int mid=(begin+end)>>1;
change(L(node),begin,mid,tree[node].tag);
change(R(node),mid+1,end,tree[node].tag);
tree[node].tag=0;tree[node].flag=0;
}
}
void update(int node,int begin,int end,int x,int y,int val){
if(begin>=x&&end<=y){
change(node,begin,end,val);
return ;
}else{
pushdown(node,begin,end);
int mid=(begin+end)>>1;
if(x<=mid)update(L(node),begin,mid,x,y,val);
if(y>mid) update(R(node),mid+1,end,x,y,val);
tree[node]=merge(tree[L(node)],tree[R(node)]);
}
}
允许有空集
前面的代码里都是不允许空集的,那么允许空集是怎样的呢?
其实只需要改一点就可以了。赋值判断一下大于0还是小于0,如果小于0就在left,right,middle赋值为0。
定义:
struct kkk{
int val,left,right,middle,tag,flag;
kkk(){left=right=middle=val=0;} //清0
}tree[maxn];
query:
kkk query(int node,int begin,int end,int x,int y){
if(begin>=x&&end<=y)return tree[node];
int mid=(begin+end)>>1;
pushdown(node,begin,end);
kkk Left,Right;
if(x<=mid) Left=query(L(node),begin,mid,x,y);
if(y>mid) Right=query(R(node),mid+1,end,x,y);
return merge(Left,Right);
}
update:
void change(int node,int begin,int end,int val){
tree[node].val=(end-begin+1)*val;
tree[node].left=tree[node].right=tree[node].middle=max(val,0); //负数不如0
tree[node].tag=val;tree[node].flag=1;
}
void pushdown(int node,int begin,int end){
if(tree[node].flag==1){
int mid=(begin+end)>>1;
change(L(node),begin,mid,tree[node].tag);
change(R(node),mid+1,end,tree[node].tag);
tree[node].tag=0;tree[node].flag=0;
}
}
void update(int node,int begin,int end,int x,int y,int val){
if(begin>=x&&end<=y){
change(node,begin,end,val);
return ;
}else{
pushdown(node,begin,end);
int mid=(begin+end)>>1;
if(x<=mid)update(L(node),begin,mid,x,y,val);
if(y>mid) update(R(node),mid+1,end,x,y,val);
tree[node]=merge(tree[L(node)],tree[R(node)]);
}
}
build:
void build(int node,int begin,int end){
if(begin==end){
tree[node].val=a[begin];tree[node].flag=0;
tree[node].left=tree[node].right=tree[node].middle=max(0,a[begin]); //负数不如0
return ;
}else{
int mid=(begin+end)>>1;
build(L(node),begin,mid);
build(R(node),mid+1,end);
tree[node]=merge(tree[L(node)],tree[R(node)]);
}
}
树上最大子段和
终于到树链剖分啦,传说什么区间问题都能搬到树上,最大子段和也不例外。
先来看看GSS7,要支持两个操作。一查询,二修改。
建树就不用说了吧。
首先修改操作,和普通树链剖分一样,用上面的区间修代码加树链剖分就OK辽。
树链剖分过程
void linkadd(int x,int y,int z){
int fx=top[x],fy=top[y];
while(fx!=fy){
if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
update(1,1,seg[0],seg[fx],seg[x],z);
x=father[fx];fx=top[x];
}
if(dep[x]>dep[y])swap(x,y);
update(1,1,seg[0],seg[x],seg[y],z);
}
重点是查询!
查询
我们知道树链剖分的核心就在于把树上的链化为序列。所以我们在树链剖分的过程中将链上的序列合并,最后得出的序列即为要求序列。
那么怎么在跳重链的过程将序列合并呢?
我们维护两个结构体L和R,分别表示左链上的序列和右链上的序列。什么意思呢?两个点x和y的最近公共祖先F只有一个。那么我们称F到x那一段为左链,F到y那一段为右链。
跳重链的过程可能是一下跳到F到x那一段,一下跳到F到y那一段,所以如果在F到x那一段,我们将那段区间加入到L,不然加入到R。
最后将L和R合并即是最后序列。小细节:要将L的left和right互换再合并。
还是挺复杂的,看看代码注释比较有帮助:
kkk linkquery(int x,int y){
kkk L,R;
int fx=top[x],fy=top[y];
while(fx!=fy){ //跳重链
if(dep[fx]<dep[fy]){
R=merge(query(1,1,n,seg[fy],seg[y]),R); //跳到了F到y那边,和R合并
y=father[fy];fy=top[y];
}else{
L=merge(query(1,1,n,seg[fx],seg[x]),L); //跳到了F到x那边,和L合并
x=father[fx];fx=top[x];
}
}
if(dep[x]>dep[y]){
L=merge(query(1,1,n,seg[y],seg[x]),L); //同上
}else{
R=merge(query(1,1,n,seg[x],seg[y]),R);
}
swap(L.left,L.right); //交换
return merge(L,R); //合并L,R
}
剩下也差不多,代码比较长,放剪切板里:GSS7代码
To be continue……
GSS系列题解——最大子段和系列的更多相关文章
- IEEE Bigger系列题解
Bigger系列题解 Bigger Python 坑点在于要高精度以及表达式求值,用java写可以很容易避免高精度问题 然后这道题就可以AC了 代码 import java.io.*; import ...
- PHP有两个不同的版本:4.x系列版本和5.x系列版本
在为用户提供动态内容方面,PHP和MySQL是一个强大的组合.这些年来,这两项产品已经跨越了它们最初的应用舞台,现在,一些世界上最繁忙的网站也在应用它们.虽然它们当初都是开源软件,只能在UNIX/Li ...
- 【系列专题】ECMAScript 重温系列(10篇全)
ES6 系列ECMAScript 2015 [ES]150-重温基础:ES6系列(一) [ES]151-重温基础:ES6系列(二) [ES]152-重温基础:ES6系列(三) [ES]153-重温基础 ...
- Scratch少儿编程系列:(十)系列总结及后续计划
一.系列文章的来由 本篇为该系列文章的一个简单总结, 从初次接触Scratch开始,在写本系列文章过程中,一边读书,一边通过例子做练习. 技术实现,对于我跟人来说,没有什么难度. 我相信,对于一个初次 ...
- GSS 系列题解
GSS GSS1 随便猫树或者线段树,就可以过了 猫树不说,线段树可以维护左边最大,右边最大,区间最大,区间值然后就做出来了. //Isaunoya #pragma GCC optimize(2) # ...
- 51Nod 最大M子段和系列 V1 V2 V3
前言 \(HE\)沾\(BJ\)的光成功滚回家里了...这堆最大子段和的题抠了半天,然而各位\(dalao\)们都已经去做概率了...先%为敬. 引流之主:老姚的博客 最大M子段和 V1 思路 最简单 ...
- 【题解】NOI 系列题解总集
每次做一道 NOI 系列的估计都很激动吧,对于我这种萌新来说( P1731 [NOI1999]生日蛋糕 练习剪枝技巧,关于剪枝,欢迎看我的垃圾无意义笔记 这道题是有一定难度的,需要运用各种高科技剪枝( ...
- QTREE系列题解
打了快一星期的qtree终于打完了- - (其实还有两题改不出来弃疗了QAQ) orz神AK一星期前就虐完QTREE 避免忘记还是简单写下题解吧0 0 QTREE1 题意: 给出一颗带边权树 一个操作 ...
- SPOJ GSS(Can you answer the Queries)系列 7/8
GSS1 线段树最大子段和裸题,不带修改,注意pushup. 然而并不会猫树之类的东西 #include<bits/stdc++.h> #define MAXN 50001 using n ...
随机推荐
- Bug搬运工-CSCux99539:Intermittent error message "Power supply 2 failed or shutdown"
Description Symptom:Following error messages will be seen intermittently.%PFMA-2-PS_FAIL: Power supp ...
- 题解【洛谷P2279】[HNOI2003]消防局的设立
题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有\(n\)个基地.起初为了节约材料,人类只修建了\(n-1\)条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成 ...
- 基于jenkins自动打包并部署docker环境及PHP环境
- queue的使用-Hdu 1702
ACboy needs your help again! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K ( ...
- 获取表格数据转换为JSON字符串
核心代码JavaScript代码: 方法一 function sc () { var myTable=document.getElementById("myTable"); //获 ...
- Java web 会话技术 cookie与session
一.会话 会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话. 会话过程中要解决的一些问题 每个用户在使用浏览器与服务器进行会话的过程 ...
- Flutter Android 真机器调试 、模拟器调试、Vscode 中开发 Flutter 应用
必备条件: 1.准备一台 Android 手机 2.手机需要开启调试模式 3.用数据线把手机连上电脑 4.手机要允许电脑进行 Usb 调试 5.手机对应的 sdk 版本必须安装 注意: 1.关闭电脑上 ...
- mcast_set_ttl函数
#include <errno.h> #include <net/if.h> #include <sys/socket.h> #include <netine ...
- python网络爬虫之解析网页的XPath(爬取Path职位信息)[三]
目录 前言 XPath的使用方法 XPath爬取数据 后言 @(目录) 前言 本章同样是解析网页,不过使用的解析技术为XPath. 相对于之前的BeautifulSoup,我感觉还行,也是一个比较常用 ...
- Eclipse Tomcat 7.0 添加WEB项目报错:Tomcat version 7.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 and 6 Web modules
前言 我叫梅乾花,我误闯了“从零开始的程序世界”,遭受到了前所未有的的困难,为了活下去,为了看见美好的明天,我开始学习之旅. 问题篇我打开了"Eclipse",将项目导入其中,开启 ...