【洛谷5069】纵使日薄西山【set】【树状数组】

传送门

最nb的就是这种算法简单思维困难的题。

题目说,选最大,将左中右三个一起减少。

使用瞪眼法你要发现一个性质:

修改一个数,那左中右的相对大小不变。所以它左右的数不会被修改,永远不会。因为其它数最多会使它们减少。

也就是说,我们要维护的其实是所有会被操作的数的和。

哪些数会被操作呢。

数列问题要考虑单调区间。

对于一个单调区间,从最高的那个点开始,我们会处理第一个,第三个,第五个,,这样的点。

也就是说,对于一个区间,我们只处理奇偶性相同的一些点。

所以我们考虑维护极端点,就是那些隔开区间的点点。用set装就行。

现在我们要想,修改一个点会影响哪些区间。举个栗子。如果原来是这样

1,9,7,9,1,。

如果把7修改成10,那首先影响了[9,7]和[7,9]这两个,变成不存在的区间。

其次使得[1,9]和[9,1]扩展了。所以会影响左右各两个区间总共四个。

在计算答案的时候,我们一个一个区间地修改,我使用了左开右闭表示。但是还有些细节:

比如这两个区间长成这个样子:5,3,8;我们发现对于[3,8]这个区间,3会被4修改。但我们一段一段计算,3会被[5,3]计算进去。所以我们要判断。也就是说,如果我和我右边的奇偶性不同,且我是低点,我就要减1。

而对于最左边那个端点,因为是左开右闭,我们要先确定这个端点是否计算。如果这个端点是一个低点,且和下一个点奇偶性相同,和上一个点奇偶性也相同,它才会在本次进入计算。否则,它早就在之前被计算了。

如此,你就可以水过去一个黑题。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define IT set<int>::iterator
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
set<int> s;int ans;
int lowbit(int u){
	return u&(-u);
}
struct node{
	int t[100003];
	void update(int u,int key){
		while(u<=100000){
			t[u]+=key;u+=lowbit(u);
		}
	}
	int query(int u,int x){
		int sum=0;
		while(x){
			sum+=t[x];x-=lowbit(x);
		}
		while(u){
			sum-=t[u];u-=lowbit(u);
		}
		return sum;
	}
}b[2];int n,a[100003],q;
#define ODD(o) ((*l^*o(j=l))&1)//表示我和下一个或者上一个的奇偶性是否相同。 
void calc(IT l,IT r,int inv){
	IT i,j;int sum=(*l&&(a[*l]<a[*l+1])&&!ODD(--)&&!(ODD(++)))?a[*l]:0;
	for(i=l++;i!=r;i=l++){
		if(a[*l]>a[*i])sum+=b[*l&1].query(*i,*l);
		else sum+=b[*i&1].query(*i,*l-ODD(++));
	}ans+=sum*inv;
}
void yzzkal(int pos){
	if((a[pos]<a[pos+1])!=(a[pos-1]<a[pos]))s.insert(pos);
	else s.erase(pos);
}
void update(int key,int pos){
	IT i=s.lower_bound(pos),l=i,r=i;
	--l;if(l!=s.begin())--l;
	++r;if(r==s.end()||(*i==pos)&&(++r==s.end()))--r;
	calc(l,r,-1);
	b[pos&1].update(pos,key-a[pos]);a[pos]=key;
	yzzkal(pos);if(pos>1)yzzkal(pos-1);if(pos<n)yzzkal(pos+1);
	calc(l,r,1);
}

signed main(){
	n=in;s.insert(0);s.insert(n+1);for(int i=1;i<=n;i++)update(in,i);
	//cout<<ans<<"# "<<endl; 
	q=in;
	while(q--){
		int x=in;int y=in;update(y,x);cout<<ans<<'\n';
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值