纵使日薄西山,我也想保护你..
先不考虑单点修改
手玩一下样例
可以发现你一直操作一个数的结果是和答案一样的
可以大致证明一下
x y z
如果选择y操作,那么x,z必然不能操作,因为x,z会和y一直减少,只有y做出了贡献,所以我们的答案应该是选的数之和
因此应该只有2种操作方法
操作1 ,3,5,7...
操作2,4,6,8...
也就是维护一个奇偶性相同的单增或者单减序列
挺抽象的,这种做法待补
我们可以用线段树来维护需要被操作的值(合并很妙)
我们先思考一下,我们需要维护哪些值,左端点有没有被操作的,右端点有没有被操作的,以及如果左端点需要被操作的贡献是多少,右端点需要被操作的贡献是多少,如果两端都被操作的贡献是多少,因此我们维护四颗线段树,seg[N<<2][4]。
0是左右两端都没有被操作
1是左端点需要被操作
2是右端点需要被操作
3是左右端点都要被操作
struct node{
ll lf,rf,val;
}seg[N<<2][4];// 1l,2r,3all,0no
感觉也很抽象我们可以举一个例子(标记X是需要被操作的,O是在其他操作范围区间的)
1 | 3 | 4 | 5 | 5 | 4 | ||
0 | X | X | X | X | |||
1 | O | X | O | X | |||
2 | X | O | X | O | |||
3 | O | X | O | O | X | O |
假设 1 3 4 5 5 4合并
1 | 3 | 4 | 5 | 5 | 4 | |
0 | X | X | X | |||
1 | O | X | X | X | ||
2 | X | X | O | |||
3 | O | X | X | O |
可以发现[0]的左边是从2转移过来的,右边从[0]转移过来的,判断依据是5>4
因此就是a[mid]与a[mid+1]的大小关系
接下来,我们就仔细分析每种情况的转移方案
1. if()在两端都不用管的情况下,如果左子树的右端点有标记,右子树的左端点有标记
(i)左边大于等于右边
那选择左边直接转移,右边转移左边有端点的那种情况,答案求和是左边(不管)+右边(左边要管)
(ii)右边大于左边
那选择右边直接转移,左边转移右边有端点的那种情况,答案求和是左边(右边要管)+右边(不管)
if(seg[tl(id)][0].rf && seg[tr(id)][0].lf){//左,右端点都存在
if(a[mid]>=a[mid+1]){//左边大
seg[id][0].lf=seg[tl(id)][0].lf;//选左边
seg[id][0].rf=seg[tr(id)][1].rf;//转移如果左边要管的右边的rf
seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][1].val;//左边(都要管)+右边(管左边)
}else{//右边大
seg[id][0].lf=seg[tl(id)][2].lf;//转移如果右边要管的左边的lf
seg[id][0].rf=seg[tr(id)][0].rf;//选右边
seg[id][0].val=seg[tl(id)][2].val+seg[tr(id)][0].val;//左边(管右边)+右边(都要管)
}
else()直接转移左右两边,答案也直接加
else{//直接拼
seg[id][0].lf=seg[tl(id)][0].lf;
seg[id][0].rf=seg[tr(id)][0].rf;
seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][0].val;
}
2.if()在要管左边的左子树有右端点标记以及不管的右子树有左端点
因为存在要管左边的,所以不用转移左边,考虑右边的转移即可
(i)左边大于等于右边
转移需要管左边的右边,答案求和是左边(管左边)+右边(管左边)
(ii)右边大于左边
直接转移,答案求和是左边(管两边)+右边(不管)
if(seg[tl(id)][1].rf && seg[tr(id)][0].lf){//左,右端点存在
if(a[mid]>=a[mid+1]){
seg[id][1].rf=seg[tr(id)][1].rf;
seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][1].val;
}else{
seg[id][1].rf=seg[tr(id)][0].rf;
seg[id][1].val=seg[tl(id)][3].val+seg[tr(id)][0].val;
}
else() 直接转移
else{
seg[id][1].rf=seg[tr(id)][0].rf;
seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][0].val;
}
3.if()不管的左子树有右端点以及管右边的右子树有左端点
同理考虑左端点的转移
(i)左边大于等于右边
转移需要不管的左边,答案求和是左边(不管)+右边(管两边)
(ii)右边大于左边
直接转移,答案求和是左边(管右边)+右边(管右边)
if(seg[tl(id)][0].rf && seg[tr(id)][2].lf){
if(a[mid]>=a[mid+1]){
seg[id][2].lf=seg[tl(id)][0].lf;
seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][3].val;
}else{
seg[id][2].lf=seg[tl(id)][2].lf;
seg[id][2].val=seg[tl(id)][2].val+seg[tr(id)][2].val;
}
else()直接转移
if(seg[tl(id)][0].rf && seg[tr(id)][2].lf){
if(a[mid]>=a[mid+1]){
seg[id][2].lf=seg[tl(id)][0].lf;
seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][3].val;
}else{
seg[id][2].lf=seg[tl(id)][2].lf;
seg[id][2].val=seg[tl(id)][2].val+seg[tr(id)][2].val;
}
4.if() 要管左边的左子树有右端点标记,要管右边的右子树有左端点标记
不用考虑左右端点转移
(i)左边大于等于右边
答案求和是左边(管左边)+右边(管两边)
(ii)右边大于左边
答案求和是左边(管两边)+右边(管右边)
if(seg[tl(id)][1].rf && seg[tr(id)][2].lf){
if(a[mid]>=a[mid+1]){
seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][3].val;
}else{
seg[id][3].val=seg[tl(id)][3].val+seg[tr(id)][2].val;
}
else()直接转移
seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][2].val;
讨论结束
可以发现只有中间存在重叠的情况才需要讨论左右端点的标记转移,其他情况直接转移即可
文字描述很拉垮,可以直接看码.
完整代码
#include<iostream>
#include<cmath>
#include<algorithm>
#include<bitset>
#include<cstring>
#include<vector>
#define INF (1ll<<30)
using namespace std;
typedef long long ll;
const int N=1e5+9;
namespace Lan {
inline string sread() {
string s=" ";char e=getchar();
while(e==' '||e=='\n')e=getchar();
while(e!=' '&&e!='\n')s+=e,e=getchar();
return s;
}
inline void swrite(string s){
for(char e:s)putchar(e);
printf("\n");
}
inline ll read() {
ll x=0,y=1;char c=getchar();
while(!isdigit(c)){if(c=='-')y=-1;c=getchar();}
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*=y;
}
inline void write(ll x) {
if(x<0){x=-x,putchar('-');}ll sta[35],top=0;
do sta[top++]=x%10,x/=10;while(x);
while(top)putchar(sta[--top]+'0');
}
}using namespace Lan;
int a[N];
struct node{
ll lf,rf,val;
}seg[N<<2][4];// 1l,2r,3all,0no
// 1 3 4
//1 X X
//2 X X
//3 X X
//0 X X
// 5 5 4
//1 X
//2 X
//3
//0 X X
ll tl(ll x){return x<<1;}
ll tr(ll x){return x<<1|1;}
void pushup(int id,int l,int r){
int mid=(l+r)>>1;
if(seg[tl(id)][0].rf && seg[tr(id)][0].lf){//左,右端点都存在
if(a[mid]>=a[mid+1]){//左边大
seg[id][0].lf=seg[tl(id)][0].lf;//选左边
seg[id][0].rf=seg[tr(id)][1].rf;//转移如果左边要管的右边的rf
seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][1].val;//左边(都要管)+右边(管左边)
}else{//右边大
seg[id][0].lf=seg[tl(id)][2].lf;//转移如果右边要管的左边的lf
seg[id][0].rf=seg[tr(id)][0].rf;//选右边
seg[id][0].val=seg[tl(id)][2].val+seg[tr(id)][0].val;//左边(管右边)+右边(都要管)
}
}else{//直接拼
seg[id][0].lf=seg[tl(id)][0].lf;
seg[id][0].rf=seg[tr(id)][0].rf;
seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][0].val;
}
if(seg[tl(id)][1].rf && seg[tr(id)][0].lf){//左,右端点存在
if(a[mid]>=a[mid+1]){
seg[id][1].rf=seg[tr(id)][1].rf;
seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][1].val;
}else{
seg[id][1].rf=seg[tr(id)][0].rf;
seg[id][1].val=seg[tl(id)][3].val+seg[tr(id)][0].val;
}
}else{
seg[id][1].rf=seg[tr(id)][0].rf;
seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][0].val;
}
if(seg[tl(id)][0].rf && seg[tr(id)][2].lf){
if(a[mid]>=a[mid+1]){
seg[id][2].lf=seg[tl(id)][0].lf;
seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][3].val;
}else{
seg[id][2].lf=seg[tl(id)][2].lf;
seg[id][2].val=seg[tl(id)][2].val+seg[tr(id)][2].val;
}
}else{
seg[id][2].lf=seg[tl(id)][0].lf;
seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][2].val;
}
if(seg[tl(id)][1].rf && seg[tr(id)][2].lf){
if(a[mid]>=a[mid+1]){
seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][3].val;
}else{
seg[id][3].val=seg[tl(id)][3].val+seg[tr(id)][2].val;
}
}else{
seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][2].val;
}
}
void build(int id,int l,int r){
if(l==r){
seg[id][0].val=a[l];
seg[id][0].lf=1;
seg[id][0].rf=1;
}else{
int mid=(l+r)>>1;
build(tl(id),l,mid);
build(tr(id),mid+1,r);
pushup(id,l,r);
}
}
void update(int id,int l,int r,int pos,int val){
if(l==r){
seg[id][0].val=val;
}else{
int mid=(l+r)>>1;
if(mid>=pos){
update(tl(id),l,mid,pos,val);
}else{
update(tr(id),mid+1,r,pos,val);
}
pushup(id,l,r);
}
}
int main(){
// ios::sync_with_stdio(false);
// cin.tie(0),cout.tie(0);
int n;
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
build(1,1,n);
int q;
q=read();
while(q--){
int x,y;
x=read(),y=read();
a[x]=y;
update(1,1,n,x,y);
cout<<seg[1][0].val<<'\n';
}
return 0;
}
再次总结,ynoi题的真好,又学了一下如何维护多个量,lxl orz