题目连接:
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)) (单点修改)
下面开始步骤分析:
-
需要维护哪些东西? l, r, sum, add, cl(区间清空懒标记)
-
如何维护信息?
(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;
}