CF1136E Nastya Hasn't Written a Legend 势能线段树/二分

原题链接:Nastya Hasn't Written a Legend

题目大意

给定长度为\(n\)的数组\(a\)和长度为\(k-1\)的数组\(k\),执行\(q\)个操作,每个操作形如:

  • \(a_i\)\(x\),之后如果有\(a_{i+1} < a_i +k_i\)则修改\(a_{i+1}\)\(a_i+k_i\),之后对\(a_{i+2}\)如果有\(a_{i+2}<a_{i+1}+k_{i+1}\)则修改\(a_{i+2}\)\(a_{i+1}+k_{i+1}\).循此往复直到不满足条件或者没有数了.
  • \([l,r]\)\(a\)的和

数据保证,在一开始时有\(a_i+k_i\leq a_{i+1}\)对任意的\(i \in [1,n-1]\).

思路

形式上来看比较像单点修改和区间求和的线段树,但是单点修改之后的附加操作非常的谔谔,因为他并不同于普通的区间操作,普通的区间操作是直接给定一个数去修改的,但是这个往后的附加操作恶心就恶心在他跟另外一个实际存在的数组相关,也就是你不能直接的把一个值推到整个区间上,所以就显得非常蛋疼.这个形式也非常像历史值的势能线段树问题,现在考虑一下怎么转换这个修改,使得他变成一个可以拿区间操作套路的形式.

事实上这个操作就是要保证对于任意的位置都保持有\(a_i\geq a_{i-1}+k_{i-1}\).对其放缩一下\(a_i\geq a_{i-2}+k_{i-1}+k_{i-2}\),继续可以推到\(a_i\geq a_1 + \sum\limits_{j=1}^{i-1}k_j\).记\(k\)的前缀和\(g_i=\sum\limits_{j=1}^i k_j\),那么也就是需要保证\(a_i\geq a_1 + g_{i-1}\),当然这个形式还是比较蛋疼,把他合并一下,令\(u_i=a_i-g_{i-1}\)形式变为\(u_i\geq a_1\).那么进一步的,我们想办法把这个形式化成只有\(u\)存在的:

因为需要保证任意的位置的条件,我们不妨尝试把这个条件等价变形成只有\(u\)的条件,因为\(a_i\geq a_{i-1}+k_{i-1}\),\(a_i=u_i+g_{i-1}\),所以可以写成\(u_i+g_{i-1}\geq u_{i-1}+g_{i-2}+k_{i-1}\)于是我们可以惊奇的发现一个非常牛逼的地方:这个条件等价于\(u_i\geq u_{i-1}\),也就是说我们本来的问题是要保证\(a\)的每一位满足这个形式,转换之后的问题就是要保证\(u\)始终是一个单调不降的序列,这就很有希望了.

考虑使用线段树维护\(u\)的值:

  • 单点修改\(a_i+=x\)可以转换成\(u_i+=x\),之后维护的操作也就是要让整个\(u\)维持单调不降,本来的操作是在违反条件的时候让\(a_{i+1}=a_i+k_i\)那么与上面相同,做一个转换可以发现这个操作其实就是让\(u_{i+1}=u_i\),那么这个操作就是等于说从\(i+1\)这个位置开始找最后一个\(u_j<u_i\),并且让之间所有违反规则的\(u_j\)修改成\(u_i\)(因为再往下一个\(i+2\)修改的时候,他的值应该拿\(u_{i+1}\)过来,但是这里拿过来的时候\(u_{i+1}=u_i\)所以等于说可以直接拿\(u_i\)去推平区间).这一步要么使用二分确定最后一个\(j\)的位置,再把整个区间推平,要么使用势能线段树去做一个区间取max的操作,我选择的是后者用势能线段树去做,不过使用的时候还有坑点,在最后会说明.
  • 求和\([l,r]\)区间,还是一样把\(a_i\)替换一下可以得到\(\sum\limits_{i=l}^r u_i + g_{i-1}\)后面的\(g_{i-1}\)单独抠一下变成\(\sum\limits_{j=l-1}^{r-1}g_j\),那么对之求二次前缀和就可以了,前者直接用线段树查询.

势能线段树的单次操作复杂度是\(O(log^2n)\)的,使用二分操作的话,线段树本身的划分特性可以使单次操作时间复杂度降到\(O(logn)\).详见洛谷题解区this.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7;
const ll INF = 1e18;
ll _a[N],a[N],k[N];
ll g[N],g_[N];
struct Node
{
	int l,r;
	ll sum = 0;
	ll mx1,mx2,mn1,mn2;
	int cntmx,cntmn;
	ll add1,add2,add3;
}tr[N * 4];

void pushup(int u)
{
	auto& s = tr[u],&lf = tr[u << 1],&rt = tr[u << 1 | 1];
	s.sum = lf.sum + rt.sum;
	
	if(lf.mx1 > rt.mx1)	s.mx1 = lf.mx1,s.cntmx = lf.cntmx,s.mx2 = max(lf.mx2,rt.mx1);
	else if(lf.mx1 < rt.mx1)	s.mx1 = rt.mx1,s.cntmx = rt.cntmx,s.mx2 = max(lf.mx1,rt.mx2);
	else	s.mx1 = lf.mx1,s.cntmx = lf.cntmx + rt.cntmx,s.mx2 = max(lf.mx2,rt.mx2);
	
	if(lf.mn1 < rt.mn1)	s.mn1 = lf.mn1,s.cntmn = lf.cntmn,s.mn2 = min(lf.mn2,rt.mn1);
	else if(lf.mn1 > rt.mn1)	s.mn1 = rt.mn1,s.cntmn = rt.cntmn,s.mn2 = min(lf.mn1,rt.mn2);
	else	s.mn1 = lf.mn1,s.cntmn = lf.cntmn + rt.cntmn,s.mn2 = min(lf.mn2,rt.mn2);
}

void modify(int u,ll k1,ll k2,ll k3)
{
	if(tr[u].mn1 == tr[u].mx1)
	{ 
		if(k1 == k3)	k1 = k2;
		else	k2 = k1;
		tr[u].sum += 1ll*k1 * tr[u].cntmn;
	}
	else	tr[u].sum += 1ll*k1 * tr[u].cntmn + 1ll*k2 * tr[u].cntmx
	                  +  1ll*k3 * (tr[u].r - tr[u].l + 1 - tr[u].cntmn - tr[u].cntmx);
	
	if(tr[u].mn2 == tr[u].mx1)	tr[u].mn2 += k2;
	else if(tr[u].mn2 != INF)	tr[u].mn2 += k3;
	
	if(tr[u].mx2 == tr[u].mn1)	tr[u].mx2 += k1;
	else if(tr[u].mx2 != -INF)	tr[u].mx2 += k3;
	
	tr[u].mn1 += k1;tr[u].mx1 += k2;
	tr[u].add1 += k1;tr[u].add2 += k2;tr[u].add3 += k3;
}

void pushdown(int u)
{
	int mx = max(tr[u << 1].mx1,tr[u << 1 | 1].mx1);
	int mn = min(tr[u << 1].mn1,tr[u << 1 | 1].mn1);
	modify(u << 1,tr[u << 1].mn1 == mn ? tr[u].add1 : tr[u].add3,
				  tr[u << 1].mx1 == mx ? tr[u].add2 : tr[u].add3,tr[u].add3);
	modify(u << 1 | 1,tr[u << 1 | 1].mn1 == mn ? tr[u].add1 : tr[u].add3,
					  tr[u << 1 | 1].mx1 == mx ? tr[u].add2 : tr[u].add3,tr[u].add3);

	tr[u].add1 = tr[u].add2 = tr[u].add3 = 0;
}

void build(int u,int l,int r)
{
	tr[u].l = l,tr[u].r = r;
	tr[u].add1 = tr[u].add2 = tr[u].add3 = 0;
	if(l == r)
	{
		tr[u].sum = a[l];
		tr[u].mn1 = a[l];tr[u].mn2 = INF;tr[u].cntmn = 1;
		tr[u].mx1 = a[l];tr[u].mx2 = -INF;tr[u].cntmx = 1;
	}
	else
	{
		int mid = l + r >> 1;
		build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
		pushup(u);
	}
}

void modify_sum(int u,int l,int r,ll v)
{
	if(tr[u].l > r || tr[u].r < l)	return ;
	if(tr[u].l >= l && tr[u].r <= r)
	{
		modify(u,v,v,v);
		return ;
	}
	pushdown(u);
	modify_sum(u << 1,l,r,v);
	modify_sum(u << 1 | 1,l,r,v);
	pushup(u);
}

void modify_max(int u,int l,int r,ll v)
{
	if(tr[u].l > r || tr[u].r < l || v <= tr[u].mn1)	return ;
	if(tr[u].l >= l && tr[u].r <= r && v < tr[u].mn2)
	{
		modify(u,v - tr[u].mn1,0,0);
		return ;
	}
	pushdown(u);
	modify_max(u << 1,l,r,v);
	modify_max(u << 1 | 1,l,r,v);
	pushup(u);
}

ll query_sum(int u,int l,int r)
{

	if(tr[u].l >= l && tr[u].r <= r)	return tr[u].sum;
	int mid = tr[u].l + tr[u].r >> 1;ll res = 0;
	pushdown(u);
	if(l <= mid)	res += query_sum(u << 1,l,r);
	if(r > mid)	res += query_sum(u << 1 | 1,l,r);
	return res;
}

int main()
{
	int n;scanf("%d",&n);
	forn(i,1,n)	scanf("%lld",&_a[i]);
	forn(i,1,n - 1)	scanf("%lld",&k[i]);
	forn(i,1,n - 1)	g[i] += g[i - 1] + k[i];
	forn(i,1,n - 1)	g_[i] += g_[i - 1] + g[i];
	forn(i,1,n)	a[i] = _a[i] - g[i - 1];
	build(1,1,n);
	
	int m;scanf("%d",&m);
	while(m--)
	{
		char op[2];scanf("%s",op);
		int l,r;scanf("%d%d",&l,&r);
		if(*op == 's')
		{
			ll res = query_sum(1,l,r);
			res += g_[r - 1] - g_[l - 2];
			printf("%lld\n",res);
		}
		else if(*op == '+')
		{
			modify_sum(1,l,l,r);
			modify_max(1,l + 1,n,query_sum(1,l,l));
		}
	}
	
    return 0;
}

注意事项

由于修改之后的值非常大,大部分地方都需要开ll,这是其一,尤其是保存最小值的部分.其次一点是单独修改\(u_i\)的值的时候,不能单纯的写一个\(u_i += x\),之后区间max操作的时候就直接用\(u_i\)操作去作为修改的值,因为每个\(u_i\)都有可能被前面的区间max操作影响,所以当前保存的\(u_i\)并不一定是真实的值,他是可能被前面区间推平的时候修改过的,所以这里必须用线段树单独的去查询一下真实值是多少作为参数.

而且这个如果没有写好类型转换的话,可能会有爆区间的问题,但是开大空间是不能解决问题的,这里有个小坑.

还有一点就是\(INF\)的值需要开大,这个题数据还是非常大的.

posted @ 2021-01-18 10:56  随处可见的阿宅  阅读(121)  评论(0编辑  收藏  举报