3110: [Zjoi2013]K大数查询
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 10703 Solved: 3209
[Submit][Status][Discuss]
Description
有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c
如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。
Input
第一行N,M
接下来M行,每行形如1 a b c或2 a b c
Output
输出每个询问的结果
Sample Input
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3
Sample Output
2
1
【样例说明】
第一个操作 后位置 1 的数只有 1 , 位置 2 的数也只有 1 。 第二个操作 后位置 1
的数有 1 、 2 ,位置 2 的数也有 1 、 2 。 第三次询问 位置 1 到位置 1 第 2 大的数 是
1 。 第四次询问 位置 1 到位置 1 第 1 大的数是 2 。 第五次询问 位置 1 到位置 2 第 3
大的数是 1 。
N,M<=50000,N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint
分析:学习了一波整体二分.
整体二分不是特别好理解. 我个人的理解是整体二分其实是把二分的权值和若干个操作给关联起来. 普通的二分一次只能回答一个询问,而整体二分能够将所有询问以多一个log的代价在一起处理.
步骤:在solve函数中完成,需要传递4个参数:L,R,l,r,分别表示操作序列的左右端点以及二分答案的l和r. 操作序列就是把所有的操作放在一个序列里. 题目中给的1~m个操作就组成了一个操作序列,这里只是把这些操作序列给提出来. l和r就是普通二分中的l和r.
如果l == r了,那么L到R中所有查询操作的答案都是l.
否则令mid = (l + r) / 2.这里的mid实际上就是二分的一个答案,接下来要做的事情就是看哪些询问操作的答案等于mid,哪些会大于或小于mid.
遍历L到R的操作序列.如果当前操作为修改操作,并且添加的数>mid,说明这个操作对我们假定答案为mid的询问操作有影响,那么在线段树中将区间[l[i],r[i]]+1,(l[i],r[i]是题目的输入). 并且加入到数组t2中. 否则加入t1.
如果为查询操作,查询[l[i],r[i]]的答案. 如果当前要查询第k大的数,之前查询的得到的答案比k小,那么当前查询操作的答案肯定比mid小,加入t1中,并且k -= 查询得到的答案. 否则直接加入t2(答案比mid大).
t1,t2完成了L到R的操作序列的分组.把对答案<mid的查询操作有影响的修改操作和答案一定<mid的查询操作都放到了t1,另外的放到了t2. 值域对应的发生改变,继续递归下去就能得到答案了. 非常神奇!
注意:线段树在每一次递归时要清空,不要直接递归清空,放一个覆盖标记即可. 50000 * 50000会爆int,用long long!
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const ll maxn = 50010; ll n,m,a[maxn],cnt,ans[maxn],id[maxn],t1[maxn],t2[maxn],cover[maxn << 2]; ll tag[maxn << 2],sum[maxn << 2],L[maxn << 2],R[maxn << 2]; struct node { ll l,r,v,id,opt; } e[maxn]; void pushup(ll o) { sum[o] = sum[o * 2] + sum[o * 2 + 1]; } void pushdown(ll o) { if (cover[o] != -1) { cover[o * 2] = cover[o * 2 + 1] = cover[o]; tag[o * 2] = tag[o * 2 + 1] = sum[o * 2] = sum[o * 2 + 1] = cover[o]; cover[o] = -1; } if (tag[o]) { tag[o * 2] += tag[o]; tag[o * 2 + 1] += tag[o]; sum[o * 2] += tag[o] * (R[o * 2] - L[o * 2] + 1); sum[o * 2 + 1] += tag[o] * (R[o * 2 + 1] - L[o * 2 + 1] + 1); tag[o] = 0; } } void build(ll o,ll l,ll r) { L[o] = l; R[o] = r; sum[o] = tag[o] = 0; cover[o] = -1; if (l == r) return; ll mid = (l + r) >> 1; build(o * 2,l,mid); build(o * 2 + 1,mid + 1,r); pushup(o); } void update(ll o,ll l,ll r,ll x,ll y,ll v) { if (x <= l && r <= y) { sum[o] += (r - l + 1) * v; tag[o] += v; return; } pushdown(o); ll mid = (l + r) >> 1; if (x <= mid) update(o * 2,l,mid,x,y,v); if (y > mid) update(o * 2 + 1,mid + 1,r,x,y,v); pushup(o); } ll query(ll o,ll l,ll r,ll x,ll y) { if (x <= l && r <= y) return sum[o]; pushdown(o); ll mid = (l + r) >> 1,res = 0; if (x <= mid) res += query(o * 2,l,mid,x,y); if (y > mid) res += query(o * 2 + 1,mid + 1,r,x,y); return res; } void solve(ll L,ll R,ll l,ll r) { if (L > R) return; if (l == r) { for (ll i = L; i <= R; i++) { ll temp = id[i]; if (e[temp].opt == 2) ans[temp] = l; } return; } ll mid = (l + r) >> 1; cover[1] = 0; ll p1 = 0,p2 = 0; for (ll i = L; i <= R; i++) { ll temp = id[i]; if (e[temp].opt == 1) { if (e[temp].v > mid) { update(1,1,n,e[temp].l,e[temp].r,1); t2[++p2] = temp; } else t1[++p1] = temp; } else { ll res = query(1,1,n,e[temp].l,e[temp].r); if (res < e[temp].v) { e[temp].v -= res; t1[++p1] = temp; } else t2[++p2] = temp; } } for (ll i = 1; i <= p1; i++) id[L + i - 1] = t1[i]; for (ll i = 1; i <= p2; i++) id[L + p1 + i - 1] = t2[i]; solve(L,L + p1 - 1,l,mid); solve(L + p1,R,mid + 1,r); } int main() { scanf("%lld%lld",&n,&m); for (ll i = 1; i <= m; i++) { scanf("%lld%lld%lld%lld",&e[i].opt,&e[i].l,&e[i].r,&e[i].v); id[i] = i; } build(1,1,n); solve(1,m,1,n); for (ll i = 1; i <= m; i++) if (e[i].opt == 2) printf("%lld ",ans[i]); return 0; }