其他分享
首页 > 其他分享> > CF1136E Nastya Hasn't Written a Legend 势能线段树/二分

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

作者:互联网

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

题目大意

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

数据保证,在一开始时有\(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\)的值:

势能线段树的单次操作复杂度是\(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\)的值需要开大,这个题数据还是非常大的.

标签:lf,rt,Hasn,CF1136E,mn1,tr,mx1,Written,mx2
来源: https://www.cnblogs.com/HotPants/p/14291676.html