(Ynoi2015) 纵使日薄西山 题解

一道耗费大量精力的数据结构题目

题目链接

原题目在这里插入图片描述
题目分析

大家应该都能意识到,每一次都去找下标最小的最大值是非常浪费时间的。而对于一个位置而言,只要被操作一次就等于这个位置要被操作a[i]次,因为两边的值同时被-1,导致两边的值永远不会被取到。
因此,我们可以对一个选定的位置(i)一直操作,直到a[i]变成0,此时a[i-1]和a[i+1]必定都变成0。如此反复,我我们的任务就变成了维护这个序列,求在这个序列中被选出的若干数的和。
这样,我们最初的思路就产生了。vis[]用于判断某一个位置是否被覆盖,对于每一次修改,在修改完成后从大到小进行排序,每选出一个位置就把两边的位置的vis置为true,如此反复,直到把所有位置都取出,则取出这些数的和就是这一次询问的答案。
复杂度是 O ( q n l o g n ) O(qnlogn) O(qnlogn)
这样我们就可以获得至少15分的好成绩啦
面对满眼的TLE,再看着出题人是lxl,我们就知道这个题目需要一些毒瘤的数据结构来维护啦。

题目再分析

由于需要取的数字是一系列较大的数,所以我们会考虑到一些单调的序列上的性质。我们发现,对于一个单调序列,我们取到的数字必定是第一大,第三大,第五大……在下标上的规律就是,取到的数下标的奇偶性必定是相同的。所以我们考虑使用树状数组(也许线段树也可以)分奇偶维护序列ai的前缀和,这样面对单调的序列就可以快速求出对应的数字和了。
那么下一个问题就是确定每一段单调序列的位置,这个问题也不难解决,我们只需要一个set来维护每一段序列的开头位置和结尾位置(就相当于是维护这个序列的极大值和极小值的位置)即可。
尽管这样,每一次修改对全部内容进行更新也产生了非常不理想的时间复杂度。所以我们考虑一次修改产生的最大的影响有多大的范围,只修改会产生影响的范围内的数据就可以减小需要耗费的时间了。
经过艰苦的找数据过程,我们发现最多会影响到两边不超过2个极值点的位置,如序列 ( 11 , 10 , 9 , 2 , 6 , 8 ) (11,10,9,2,6,8) (11,10,9,2,6,8),极值位置有 ( 1 , 4 , 6 ) (1,4,6) (1,4,6),将第3位的9改成1,会导致 [ 4 , 6 ] [4,6] [4,6]段变成 [ 3 , 6 ] [3,6] [3,6]段,影响到了右数第二个极值位置(6);而对于一个上凸的序列 ( 2 , 5 , 8 , 6 , 3 ) (2,5,8,6,3) (2,5,8,6,3),极值位置有 ( 1 , 3 , 5 ) (1,3,5) (1,3,5),修改第四位的6为9,影响到了 [ 1 , 3 ] [1,3] [1,3]段,变为了 [ 1 , 4 ] [1,4] [1,4]段,影响了向左数第二个极值点。
所以对于每一次修改,我们都把它两边的两个极值点之间的区间进行更新,就可以保证全部覆盖每一次修改的影响范围。而答案自然就是(上一次的答案-之前的区间的答案贡献 + 修改之后的答案贡献)。
在大框架完成之后,需要考虑的问题是极小值是否需要计算的问题。方法是先都不计算极小值的部分,需要计算的情况只有与左右两边的极大值之间距离均为偶数(下标奇偶性相同),所以在计算的时候加一步讨论就可以解决。同时计算的时候还需要分递增序列和递减序列进行考虑
其他还有许许多多的边界问题需要考虑和讨论,在这部分我出锅的主要原因是set中begin()是真实存在的,而end()是不存在的。统计答案的时候端点达到begin是可以的,但是达到end是不可以的。
在具体实现上,我们发现输入原始序列就相当于是对一个全0的序列进行若干次修改,所以也同样可以使用上面的方法,这样也减轻了代码的压力

XBB

菜鸡讨论了一下午,终于解决了需要考虑的所有问题,A掉了两年前加到题单里的题目,将近3KB的代码QAQ
在这里插入图片描述
代码已经不成人形了5555

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
const int MAXN = 1e5 + 100;
typedef long long ll;
typedef set<int>::iterator SIT;
#define DEBUG cerr << "OK";
int a[MAXN],n;
struct TreeArray{
	ll s[MAXN];
	int N;
	inline int lowbit(int num)
	{
		return num & (-num);
	}
	void add(int pos, int val)
	{
		for (int i = pos; i <= N; i += lowbit(i))
			s[i] += 1LL * val;
	}
	ll query(int pos)
	{
		int cnt = 0;
		ll sum = 0; 
		for (int i = pos ; i; i -= lowbit(i),cnt++)
			sum += s[i];
		return sum;
	}
}arr[2];

set<int> s;
ll ans;

inline int re()
{
	int num = 0, minus = 1;
	char ch = getchar();
	while (ch > '9' || ch < '0')
	{
		if (ch == '-')
			minus = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		num = (num << 1) + (num << 3) + ch - '0';
		ch = getchar();
	}
	return num * minus;
}

// get the peak or valley sum 
ll getSum(SIT l, SIT r)
{
	ll ret = 0;
	while (l != r)
	{
		SIT it = --r;
		++r;
		if(a[*it] < a[*r])
		{
			//递增
			int curPos = *r;
			ret += arr[curPos & 1].query(curPos) - arr[curPos & 1].query(*it);
		}
		else 
		{
			//递减
			int curPos = *it;
			ret += arr[curPos & 1].query(*r - 1) - arr[curPos & 1].query(curPos);

			//极小值是否需要被加
			SIT moreRight = ++r;
			--r;
			SIT moreLeft = r;
			if (moreLeft != s.begin()) 
				moreLeft--;
			if (moreRight == s.end())
				moreRight --;
			if (!((*moreRight - *r) & 1) && !((*r - *moreLeft) & 1))
				ret += a[*r];
		}
		r--;
	}
	if (a[*l] >= a[*l + 1])
		return ret;
	//判断边界最小值是否需要加,跟上面完全一样
	SIT moreRight = ++r;
	--r;
	SIT moreLeft = r;
	//moreLeft --;
	if (moreLeft != s.begin()) 
		moreLeft--;
	if (moreRight == s.end())
	moreRight --;
	if (!((*moreRight - *r) & 1) && !((*r - *moreLeft) & 1))
			ret += a[*r];
	
	return ret;
}
inline void checkValid(int pos)
{
	if((a[pos] > a[pos - 1]) == (a[pos + 1] > a[pos]))
		s.erase(pos);
	else 
		s.insert(pos);
}

void update(int pos, int num)
{
	//to get the influenced segment
	SIT it = s.lower_bound(pos);
	SIT r = it,l=it;
	r++,l--;
	if (r != s.end() && (*it) == pos) 
		r++;
	if(r == s.end())
		r--;
	if (l != s.begin())
       	l--;
	ans -= getSum(l, r);
	
	//更新内容
	arr[pos & 1].add(pos, num - a[pos]);
	a[pos] = num;
	checkValid(pos);
	if(pos != 1) 
		checkValid(pos - 1);
	if(pos != n) 
		checkValid(pos + 1);
		
	ans += getSum(l, r);
}	




int main()
{
	n = re();
	arr[0].N = arr[1].N =n + 1;
	int tmp = 0;
	s.insert(0);
	s.insert(n + 1);
	for (int i = 1; i <= n; ++i)
	{
		tmp = re();
		update(i,tmp);
	}
	int q = re();
	//for(SIT i = s.begin();i != s.end();++i)
	//	cerr << *i << ' ';

	while(q--)
	{
		int pos = re(), val = re();
		update(pos, val);
		printf("%lld\n",ans);
	}
		



	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值