您的位置:首页 > 其它

[bzoj3110][Zjoi2013]K大数查询

2013-08-05 22:06 435 查看
好题!

法1:看到题目,首先想到的便是树套树。按照一般想法,第一维是区间,第二维权值,不好想(至少我不会。。。据说有人这么干,orz)

如果反过来,做法就十分清晰了。对于区间[l,r],将权值在此之内的修改建立一棵普通线段树。这样对于一个询问,就可以类似二分答案,首先看权值在[1,mid]中有几个在询问的区间中,如果<排名,就往右,否则往左。

/**************************************************************
Problem: 3110
User: lazycal
Language: C++
Result: Accepted
Time:8436 ms
Memory:201624 kb
****************************************************************/

#include <cstdio>
const int N = 50000+9,M = N * 16 * 16;
int root[N * 4],n,m,sum[M],lc[M],rc[M],lazy[M],c,L,R,cnt;
inline int min(const int &a,const int &b){return a>b?b:a;}
inline int max(const int &a,const int &b){return a>b?a:b;}
int count(const int idx,const int l,const int r)
{
if (L <= l && r <= R) return sum[idx];
int t1 = 0,t2 = 0,mid = (l+r)/2;
if (L <= mid) t1 = count(lc[idx],l,mid);
if (mid < R)  t2 = count(rc[idx],mid + 1,r);
return (t1 + t2) + (min(r,R)-max(L,l) + 1) * lazy[idx];
}
int query()
{
int l = 1, r = n, now = 1;
for (;l != r;) {
int mid = (l + r)/2, tmp;
if ((tmp = count(root[now*2],1,n)) >= c) r = mid,now *= 2;
else l = mid + 1,now = now*2 +1,c -= tmp;
}
return l;
}
void modify(int &idx,const int l,const int r)
{
if (!idx) idx = ++cnt;
if (L <= l && r <= R) return (void)(sum[idx] += r - l + 1, ++lazy[idx]);
int mid = (l + r)/2;
if (L <= mid) modify(lc[idx],l,mid);
if (mid < R) modify(rc[idx],mid + 1,r);
sum[idx] = sum[lc[idx]] + sum[rc[idx]] + lazy[idx] * (r - l + 1);
}
void update()
{
int l = 1, r = n, now = 1;
for (;l != r;) {
int mid = (l + r)/2;
modify(root[now],1,n);
if (mid < c) l = mid + 1,now = now*2 + 1;
else r = mid,now *= 2;
}
modify(root[now],1,n);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("3110.in","r",stdin);
freopen("3110.out","w",stdout);
#endif
scanf("%d%d",&n,&m);
while (m--) {
int flag;
scanf("%d%d%d%d",&flag,&L,&R,&c);
if (flag == 1) c = n - c + 1,update();
else printf("%d\n",n-query()+1);
}
}


  

法2:分治(orz)

同样是二分,令solve(l,r)代表解决ans = l...r的询问 (什么?我怎么知道ans在哪个区间?别急……),很明显,solve(1,n)即为所求。

对于solve(l,r),任务就是将询问分组。分组等价于判断ans 与 (l+r)/2 的大小关系。也就是在[l,(l+r)/2]的数够不够k个。

处理这个问题,可以将要插入的数中<(l+r)/2的数插入到数据结构中,比如这个操作区间是L[i],R[i],那就把L[i]...R[i]中每个数+1。这样判断ans 与 (l+r)/2 的大小关系时候直接在数据结构中查询,就可以得到在[l,(l+r)/2]的且在询问区间的数的个数。然后完成分组,solve(l,(l+r)/2) solve((l+r)/2+1,r)

对于数据结构的选择,个人推荐使用树状数组。不过得懂得树状数组如何改段求段。下面附上树状数组改段---->改点的推导:


delta[i] = A[i] - A[i - 1] {1 <= i <= n}
Add(s,t,c):
  //A[s] += c;
  delta[s] = A[s] + c - A[s - 1] = delta[s] + c;
  delta[s] += c
  delta[s]*s += s*c
  //A[t] += c;
  delta[t + 1] = A[t + 1] - (A[t] + c) = delta[t + 1] - c;
  delta[t + 1] -= c
  delta[t + 1]*(t + 1) -= c*(t + 1)
Sum(s,t,c):
  A[s] + ... + A[t]
  A[s] = A[s - 1] + delta[s] = A[s - 2] + delta[s] + delta[s - 1] = ... = delta[1] + ... + delta[s]
  A[s] + ... + A[t] = (delta[1] + ... + delta[s]) * (t - s + 1) + (t - s - i + 1) * delta[s + i] {1 <= i <= t - s}
  = (delta[1] + ... + delta[s]) * (t - s + 1) + (t + 1 - (s + i)) * delta[s + i] {1 <= i <= t - s}



/**************************************************************
Problem: 3110
User: lazycal
Language: C++
Result: Accepted
Time:1560 ms
Memory:3148 kb
****************************************************************/

#include <cstdio>
const int N = 50000 + 9;
int a1[2]
,a2[2]
,n,m,ans
,a
,b
,c
,tmp1
,tmp2
,times,t
,flag
;
int count(int (&data)[2]
,int x)
{
int res = 0;
for (;x;x -= x & -x)
if (data[0][x] == times) res += data[1][x];
return res;
}
void add(int (&data)[2]
,int x,const int d)
{
for (;x <= n;x += x & -x)
if (data[0][x] == times) data[1][x] += d;
else data[0][x] = times, data[1][x] = d;
}
int count(const int s,const int t)
{
return count(a1,s) * (t - s + 1) + (t + 1) * (count(a1,t) - count(a1,s)) - (count(a2,t) - count(a2,s));
}
void add(const int s,const int t,const int c)
{
add(a1,s,c); add(a2,s,s*c);
add(a1,t + 1,-c); add(a2,t + 1, -c*(t + 1));
}
void solve(const int l1,const int r1,const int l,const int r)
{
if (l1 > r1) return ;
if (l == r) {
for (int i = l1; i <= r1; ++i)
if (flag[t[i]] == 2) ans[t[i]] = l;
return ;
}
int tmp; ++times;
tmp1[0] = tmp2[0] = 0;
const int mid = (l + r)/2;
for (int i = l1; i <= r1; ++i)
if (flag[t[i]] == 1)
if (c[t[i]] <= mid) {
tmp1[++tmp1[0]] = t[i];
add(a[t[i]],b[t[i]],1);
}else tmp2[++tmp2[0]] = t[i];
else
if ((tmp = count(a[t[i]],b[t[i]])) < c[t[i]]) {
c[t[i]] -= tmp;
tmp2[++tmp2[0]] = t[i];
}else tmp1[++tmp1[0]] = t[i];

int mid1 = tmp1[0] + l1 - 1;
for (int i = l1; i <= mid1; ++i) t[i] = tmp1[i - l1 + 1];
for (int i = mid1 + 1; i <= r1; ++i) t[i] = tmp2[i - mid1];

solve(l1, mid1, l, mid);
solve(mid1 + 1, r1, mid + 1, r);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("3110_2.in","r",stdin);
freopen("3110_2.out","w",stdout);
#endif
scanf("%d%d",&n,&m);
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d%d",flag+i,a+i,b+i,c+i);
if (flag[i] == 1) c[i] = n - c[i] + 1;
t[i] = i;
}
solve(1,n,1,n);
for (int i = 1; i <= m; ++i)
if (flag[i] == 2) printf("%d\n",n-ans[i]+1);
}


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: