前言
超级毒瘤数据结构题!
确认过眼神,是我搞不出来的题
题目
这个假期,小h在自家院子里种了许多花,它们围成了一个圈,从1..n编号(n<=100000),小h 对每盆花都有一个喜好值xi,(-1000<=xi<=1000),小h现在觉得这样一成不变很枯燥,于是他做了m(m<=100000)个改动,每次把第ki盘花改成喜好值为di的花,然后小h要你告诉他,在这个花圈中,连续的最大喜好值是多少。
Input
第一行,n,花盆的数量
第二行,n个数,表示对于每盆花的喜好值。
第三行:m, 改动的次数
以下m行,每行两个数ki 和di 。
Output
M行,每一行对应一个更改,表示连续的最大喜好值,且不能整圈都选。(注意:是在圈上找)
Sample Input
5 3 -2 1 2 -5 4 2 -2 5 -5 2 -4 5 -1
Sample Output
4 4 3 5
分析
毒瘤数据结构题,方法更新了好多次(“维护5个东西”的那个方法好像有Bug,不清楚),最后大约如下:
思路:
利用整体减空白的想法
找到区间中最小的一段,
然后用整体减去他就能得到最大字段和了
如果遇到了选了整段的情况,就减一个最小的数就行了
维护7个东西:(但是我自己觉得很奇怪)
1.最大前缀和;2.最小前缀和;3.最大后缀和;4.最小后缀和;5.最大子段和;6.最小子段和;7.总和
然后就是“合并”的问题了,自己想想吧2333....
维护5个东西”的那个方法好像有Bug,老师讲完感觉不对,出了个数据,果真把自己hack了
例如:9 -5 -5 11,
最大前缀 = 9-5-5+11 = 10,然而如果只取11,答案会更优,所以emmm...好像说是方法有点问题?
最后他们说取个什么min,没听清... ...反正我现在很混乱...
贴一个LZC大佬的讲解...
T3:
考虑维护序列的最大子段和和最小子段和,答案要么是最大子段和,要么是序列的和 - 最小子段和,这样就能考虑到环的情况。
可以用线段树维护,注意不能全部都选!
这样的话,可以用线段树维护区间的答案,然后输出 的答案和 的答案取个 max,这样就不会都选了。
哦听说有人不会用线段树维护最大 / 最小子段和?那我来补一下。
只说最大的,最小的类似。
在线段树每个结点上维护这个区间的和、最大前缀和、最大后缀和、最大子段和。
那么合并儿子信息的时候,区间和很简单;最大前缀和有两种选择,一种是左儿子的区间和 + 右儿子的最大前缀和,一种是左儿子的最大前缀和;后缀和类似;最大子段和要么是左儿子的最大子段和,要么是右儿子的最大子段和,要么是左儿子的最大后缀和 + 右儿子的最大前缀和。
然后如果还要询问区间的话就类似地拼一下,就是代码长了点(对于我这种长期写树套树的数据结构选手来说还好还好)
另一个大佬:https://blog.csdn.net/ha_ing/article/details/99462749
简要思路:本题是要在一个环上维护最大区间和,但区间不能包括整个环。考虑到一个环可能会从最大和区间断开,我们还可以维护一个最小区间和(整个数列由一个最大和区间跟一个最小和区间构成,可用反证法证明,并且环的断点只可能断开两个区间中的一个),用环的总值减去最小区间和,得到另一个预选答案,将两者比较输出较大值即可。
区间维护要利用区间合并,具体方法见代码吧。
本题还要特判,当环上所有数为正数时,我们维护的最大区间和恰好等于环的总值,此时我们维护的最小区间和也正好为环上最小的一个数,此时答案只能用环的总值减去最小区间和得出。
AC代码
我蒟蒻,自己打不来qwq... ...
特别鸣谢:https://blog.csdn.net/ha_ing/article/details/99462749
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls pos<<1
#define rs pos<<1|1
using namespace std;
const int MAXN=1e5,INF=0x3f3f3f3f;
int n,m;
struct segment_tree
{
//意义根据单词就可以猜出来了
//lmax:包括最左端的最大区间和,rmax:包括最右端的最大区间和,midmax:数列范围内最大区间和,min同理
int sum[MAXN*4+5];
int lmax[MAXN*4+5],rmax[MAXN*4+5],lmin[MAXN*4+5],rmin[MAXN*4+5];
int midmax[MAXN*4+5],midmin[MAXN*4+5];
void update(int pos)
{
sum[pos]=sum[ls]+sum[rs];
lmax[pos]=max(lmax[ls],sum[ls]+lmax[rs]);
rmax[pos]=max(rmax[rs],sum[rs]+rmax[ls]);
lmin[pos]=min(lmin[ls],sum[ls]+lmin[rs]);
rmin[pos]=min(rmin[rs],sum[rs]+rmin[ls]);
//合并右儿子的lmax与左儿子的rmax可得到父亲节点的midmax :
midmax[pos]=max(lmax[rs]+rmax[ls],max(midmax[ls],midmax[rs]));
midmin[pos]=min(lmin[rs]+rmin[ls],min(midmin[ls],midmin[rs]));
return ;
}
void pre(int pos,int l,int r)
{
sum[pos]=lmax[pos]=rmax[pos]=midmax[pos]=-INF;
lmin[pos]=rmin[pos]=midmin[pos]=INF;
if(l==r)
return ;
int mid=(l+r)/2;
pre(ls,l,mid);
pre(rs,mid+1,r);
return ;
}
void change(int pos,int aim,int val,int l,int r)
{
if(l==r)
{
sum[pos]=lmin[pos]=lmax[pos]=rmin[pos]=rmax[pos]=midmin[pos]=midmax[pos]=val;
return ;
}
int mid=(l+r)/2;
if(mid>=aim)
change(ls,aim,val,l,mid);
else
change(rs,aim,val,mid+1,r);
update(pos);
return ;
}
}tree;//用结构体封装线段树
int main()
{
scanf("%d",&n);
tree.pre(1,1,n);
int tmp;
for(int i=1;i<=n;i++)
{
scanf("%d",&tmp);
tree.change(1,i,tmp,1,n);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int k,d;
scanf("%d%d",&k,&d);
tree.change(1,k,d,1,n);
if(tree.sum[1]==tree.midmax[1])//不能包括整个环
printf("%d\n",tree.sum[1]-tree.midmin[1]);
else
printf("%d\n",max(tree.sum[1]-tree.midmin[1],tree.midmax[1]));
}
return 0;
}