迪拜的超市(思维 + 二分 + 线段树 + 懒标记)

题目连接:
https://ac.nowcoder.com/acm/contest/26896/1014

终于过了…
特别感谢这份代码,让我找到了错误orz:
https://ac.nowcoder.com/acm/contest/view-submission?submissionId=52207983

分析:

思路是对的, 卡在细节问题上面…

大题思路:
把每个商店当作线段树坐标, 物品数当作值;
询问的时候二分商店(显然是单调的), 线段树查询就行;

关键问题是: 买完之后需要清空(区间修改操作都 变成0);
此时我们就要维护一个区间清空的懒标记;
假设r是我们二分得到的答案, 那么[1,r-1]的商店是全部都清空;
而第r个商店要 -(k-query(1,1,r-1)) (单点修改)

下面开始步骤分析:

  1. 需要维护哪些东西? l, r, sum, add, cl(区间清空懒标记)

  2. 如何维护信息?
    (1)pushup时: 这个简单,区间和.

    (2)有多个懒标记了, 要考虑 懒标记的优先级 和 懒标记之间的影响 (最关键)
    1)先考虑懒标记之间的影响(此时有 cl 和 add)
    当传过来cl标记时,我们发现当前区间都变成0,即前面的add没用了,即当前区间的add标记置0;
    当传过来add标记时,我们发现对已有的cl标记是没有影响的,因为cl要对下面的区间造成影响,因此cl要保留;
    2)懒标记的优先级
    由上面的分析可知,我们要先更新cl标记,再更新add标记

    (3)pushdown时 和 打懒标记时 要怎么维护?
    1)传过来cl标记时:
    当前区间和置为0, 当前区间add标记清空, 当前区间cl清空
    两个子区间的add也要清空(不然这个add就会对下面造成影响!!!)
    2)传过来add标记时:
    这个就简单了,和一般维护区间和一样

细节

我发现一个问题! ! !
在pushdown里传cl标记时把 tr[u].add = 0; 清空是错的! ! !

想了半天, 终于想明白了!
假设区间[1,2]有cl标记,此时在[1,4]有一个add标记;
如果我们找[3,3]此时[1,4]是要裂开的,即add下传到[1,2];
此时[1,2]既有cl标记,也有add标记(add是后面才来的);
此时我们要找[1,1]时,[1,2]要裂开;
由于时cl优先更新, 若此时cl更新时把[1,2]的add置为0了,那么显然不对啊! ! !
(因为add是后面才来的,显然是要对下面产生影响的! ! !)
因此是在cl更新时,add是不能清空的;

而在我们打cl懒标记时, 显然此时区间和置为0;
此时区间的add就可以置为0了! ! !(没有后效性!)

所以我们在考虑信息维护时, pushdown 和 打懒标记时 是要分开分析的! 不完全相同!

代码

int n,m;
struct node
{
	int l,r;
	ll sum;
	ll add;
	bool cl;	//是否当前区间清空,0否,1是
	
}tr[maxn*4];

void pushup(int u)
{
	tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}

void pushdown(int u)	//两个懒标记不能同时存在
{
	if(tr[u].cl)	//先更新cl标记, 需要清空
	{
		tr[u<<1].cl = true;
		tr[u<<1].sum = 0;
		tr[u<<1].add = 0;   //注意两个子区间的懒标记也要清空
		
		tr[u<<1|1].cl = true;
		tr[u<<1|1].sum = 0;
		tr[u<<1|1].add = 0; //注意两个子区间的懒标记也要清空
		
		tr[u].cl = false;
// 		tr[u].add = 0;  //当前区间的add也要清空(错误的)
	}
	
	if(tr[u].add)	//需要加
	{
		tr[u<<1].add += tr[u].add;
		tr[u<<1].sum += (ll)(tr[u<<1].r - tr[u<<1].l + 1)*tr[u].add;
		
		tr[u<<1|1].add += tr[u].add;
		tr[u<<1|1].sum += (ll)(tr[u<<1|1].r - tr[u<<1|1].l + 1)*tr[u].add;
		
		tr[u].add = 0;
	}
}

void build(int u,int l,int r)	//建树
{
	if(l == r)
	{
		tr[u] = {l,r,0,0,0};
		return;
	}
	
	tr[u] = {l,r};
	
	int mid = l + r >> 1;
	build(u<<1, l, mid);
	build(u<<1|1, mid+1, r);
	
	pushup(u);
}

ll query(int u,int l,int r)	//区间查询
{
	if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
	
	pushdown(u);	//分裂了,下传懒标记
	
	int mid = tr[u].l + tr[u].r >> 1;
	ll sum = 0;
	
	if(l <= mid) sum += query(u<<1, l, r);
	if(r > mid) sum += query(u<<1|1, l, r);
	
	return sum;
}

void update1(int u,int l,int r,int v)   //区间+x
{
	if(l > r) return;
	if(tr[u].l >= l && tr[u].r <= r)
	{
		//注意add是没有包括自己区间的哦
		tr[u].sum += (ll)(tr[u].r - tr[u].l + 1) * v;
		tr[u].add += v;
		
		return;
	}
	
	pushdown(u);	//分裂了,下传懒标记
	
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) update1(u<<1, l, r, v);
	if(r > mid) update1(u<<1|1, l, r, v);
	
	pushup(u);	//向上更新
}

void update2(int u,int l,int r)	//区间清空
{
	if(l > r) return;
	if(tr[u].l >= l && tr[u].r <= r)
	{
		//注意add是没有包括自己区间的哦
		tr[u].sum = tr[u].add = 0;
		tr[u].cl = 1;
		
		return;
	}
	
	pushdown(u);	//分裂了,下传懒标记
	
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) update2(u<<1, l, r);
	if(r > mid) update2(u<<1|1, l, r);
	
	pushup(u);	//向上更新
}

void solve()
{
	iire(n,m);
	build(1,1,n);
	
	while(m--)
	{
		int op;
		ire(op);
		
		if(op == 1)	//区间+x
		{
			int l,r,x;
			iire(l,r); ire(x);
			
			update1(1,l,r,x);
		}
		else if(op == 2)	//查询
		{
			int k;
			ire(k);
			
			int l = 1;
			int r = n+1;
			while(l < r)
			{
				int mid = l + r >> 1;
				if(query(1,1,mid) >= k) r = mid;
				else l = mid + 1;
			}
			
			if(r == n+1) cout<<"Trote_w is sb\n";	//所有商店加起来都不行
			else
			{
				cout<<r<<endl;
				
				ll ans = k - query(1,1,r-1);
				
				//买完减去商品的操作,[1,r-1]全部清空,剩下的再-去
				update2(1,1,r-1);
				update1(1,r,r,-ans);
			}
		}
	}
}

int main()
{
	int t;
	ire(t);
	while(t--)
	{
		solve();
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值