树套树专题——bzoj 3110: [Zjoi2013] K大数查询 & 3236 [Ahoi2013] 作业 题解

时间:2022-09-12 14:08:46

【原题1】

3110: [Zjoi2013]K大数查询

Time Limit: 20 Sec  Memory Limit: 512 MB

Submit: 978  Solved: 476

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

2 5

1 1 2 1

1 1 2 2

2 1 1 2

2 1 1 1

2 1 2 3

Sample Output



1

2

1

HINT

N,M<=50000,N,M<=50000



a<=b<=N



1操作中abs(c)<=N



2操作中abs(c)<=Maxlongint

【传送门】感谢这位大牛给我的启示。

http://www.cnblogs.com/lazycal/archive/2013/08/05/3239304.html

【分析】一直听到过有一种奇妙的数据结构——树套树。

于是通过这道题我開始接触这样的算法。

树套树的本质就是两棵树套在一起(一般最外层的都是线段树)。对于当前的这棵树的每一个结点能够再开一棵树来维护。由于会爆内存,所以注意要动态开结点。说起来有点玄乎,先看看这道题吧。

我们能够先开一颗权值线段树。对于当前结点K,它表示了权值范围为a~b的全部结点的信息。可是有人要问:这样怎么控制位置范围是l~r这个要求呢?我们能够在这个点上再开一棵树套树专题——bzoj 3110: [Zjoi2013] K大数查询 &amp; 3236 [Ahoi2013] 作业 题解表示位置的信息。那么在第二重树中的结点sum[k]表示在a~b的权值范围内,位置范围是l~r的点的个数。查找的时候是BST原理。

【代码】(第一次写树套树。所以大部分借鉴了那个大牛。事实上还是比較好理解的O(∩_∩)O~~)

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=50000+5;
const int M=N*16*16;
int root[N*4],n,m,sum[M],left[M],right[M],lazy[M],c,L,R,cnt,i,opt;
inline int Read()
{
char ch=getchar();for (;ch<'0'||ch>'9';ch=getchar());
int x=0;for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
void put(int &k,int l,int r)
{
if (!k) k=++cnt;
if (L<=l&&r<=R) {lazy[k]++;sum[k]+=(r-l+1);return;}
int mid=(l+r)/2;
if (L<=mid) put(left[k],l,mid);
if (R>mid) put(right[k],mid+1,r);
sum[k]=sum[left[k]]+sum[right[k]]+lazy[k]*(r-l+1);
}
void update(int now,int l,int r)
{
put(root[now],1,n);
if (l==r) return;int mid=(l+r)/2;
if (c<=mid) update(now*2,l,mid);
else update(now*2+1,mid+1,r);
}
int calc(int k,int l,int r)
{
if (!k) return 0;
if (L<=l&&r<=R) return sum[k];
int mid=(l+r)/2,temp=0;
if (L<=mid) temp+=calc(left[k],l,mid);
if (R>mid) temp+=calc(right[k],mid+1,r);
return temp+lazy[k]*(min(R,r)-max(L,l)+1);
}
int ask(int now,int l,int r)
{
if (l==r) return l;
int mid=(l+r)/2,temp=calc(root[now*2],1,n);
if (c<=temp) return ask(now*2,l,mid);
c-=temp;return ask(now*2+1,mid+1,r);
}
int main()
{
n=Read();m=Read();
for (i=1;i<=m;i++)
{
opt=Read();L=Read();R=Read();c=Read();
if (opt==1) c=n-c+1,update(1,1,n);
else printf("%d\n",n-ask(1,1,n)+1);
}
return 0;
}

【原题】

3236: [Ahoi2013]作业

Time Limit: 100 Sec  Memory Limit: 512 MB

Submit: 533  Solved: 225

Description

树套树专题——bzoj 3110: [Zjoi2013] K大数查询 &amp; 3236 [Ahoi2013] 作业 题解

Input

树套树专题——bzoj 3110: [Zjoi2013] K大数查询 &amp; 3236 [Ahoi2013] 作业 题解

Output

树套树专题——bzoj 3110: [Zjoi2013] K大数查询 &amp; 3236 [Ahoi2013] 作业 题解

Sample Input

3 4

1 2 2

1 2 1 3

1 2 1 1

1 3 1 3

2 3 2 3

Sample Output

2 2

1 1

3 2

2 1

HINT

N=100000,M=1000000

【分析】这道题想熟练一下树套树。果断自己码代码。

第一问似乎比前面一题更加简单,由于连lazy操作都不用,仅仅要单点查询,区间询问就可以。

第二问真是费脑筋。想写树套树也没什么事,可惜想法会复杂的多,像我这样的刚開始学习的人还是算了~~那怎么办呢?我又想到了莫队算法!首先对于l,r,依照莫队对它排序一下。由于要处理a~b的权值范围,我们还要用树状数组来维护单点改动和区间询问。

【代码1】

#include<cstdio>
#include<algorithm>
#include<cmath>
#define LL(x) (x&-x)
#define N 100005
#define M 17*17*N
#define Q 1000005
using namespace std;
int n,m,i,cnt,x,y,L,R,s,l,r,t1,t2,Num,ans;
int sum[M],left[M],right[M],root[N*3],data[N],pos[N],ans1[Q],ans2[Q],f[N],flag[N];
struct HHD{int l,r,id,x,y;}a[Q];
bool cmp(HHD a,HHD b)
{
if (pos[a.l]!=pos[b.l]) return a.l<b.l;
if (pos[a.l]&1) return a.r<b.r;else return a.r>b.r;
}
inline int Read()
{
char ch=getchar();for (;ch<'0'||ch>'9';ch=getchar());
int x=0;for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
void tree(int &k,int l,int r)
{
if (!k) k=++cnt;sum[k]++;
if (l==r) return;int mid=(l+r)>>1;
if (i<=mid) tree(left[k],l,mid);
else tree(right[k],mid+1,r);
}
void update(int k,int l,int r)
{
while (l!=r)
{
tree(root[k],1,n);
int mid=(l+r)>>1;
if (data[i]<=mid) r=mid,k*=2;
else l=mid+1,k=k*2+1;
}
tree(root[k],1,n);
}
int calc(int k,int l,int r)
{
if (L<=l&&r<=R||!k) return sum[k];
int mid=(l+r)>>1,o=0;
if (L<=mid) o+=calc(left[k],l,mid);
if (R>mid) o+=calc(right[k],mid+1,r);
return o;
}
int ask(int k,int l,int r)
{
if (x<=l&&r<=y) return calc(root[k],1,n);
if (!root[k]) return 0;
int mid=(l+r)>>1,o=0;
if (x<=mid) o+=ask(k*2,l,mid);
if (y>mid) o+=ask(k*2+1,mid+1,r);
return o;
}
inline void add(int x,int c){for (;x<=n;x+=LL(x)) f[x]+=c;}
inline int Sum(int x){int o=0;for (;x;x-=LL(x)) o+=f[x];return o;}
int main()
{
freopen("3236.in","r",stdin);
freopen("3236.out","w",stdout);
n=Read();m=Read();
for (i=1;i<=n;i++)
data[i]=Read(),update(1,1,n);
s=int(sqrt(n));
for (i=1;i<=n;i++) pos[i]=i/s+1;
for (i=1;i<=m;i++)
{
L=Read(),R=Read(),x=Read(),y=Read();
a[i].l=L;a[i].r=R;a[i].x=x;a[i].y=y;a[i].id=i;
ans1[i]=ask(1,1,n);
}
sort(a+1,a+m+1,cmp);
l=1;r=1;flag[data[1]]=1;add(data[1],1);
for (i=1;i<=m;i++)
{
while (r<a[i].r) {flag[data[++r]]++;if (flag[data[r]]==1) add(data[r],1);}
while (l>a[i].l) {flag[data[--l]]++;if (flag[data[l]]==1) add(data[l],1);}
while (r>a[i].r) {flag[data[r]]--;if (!flag[data[r]]) add(data[r],-1);r--;}
while (l<a[i].l) {flag[data[l]]--;if (!flag[data[l]]) add(data[l],-1);l++;}
ans2[a[i].id]=Sum(a[i].y)-Sum(a[i].x-1);
}
for (i=1;i<=m;i++)
printf("%d %d\n",ans1[i],ans2[i]);
return 0;
}

【超时!】底下測70s。交上去就T了。

哎!这么办呢?通过调试,我发现树套树M*LOG(N)^2的时间效率还是莫队的M*LOG(N)*SQRT(N)的效率快!

!于是忍痛割爱把树套树也改成了莫队,然后底下40s,交上去60s过了。

【代码2】

#include<cstdio>
#include<algorithm>
#include<cmath>
#define LL(x) (x&-x)
#define N 100005
#define Q 1000005
using namespace std;
int n,m,i,cnt,x,y,L,R,s,l,r,t1,t2,Num,ans;
int data[N],pos[N],ans1[Q],ans2[Q],f[N],flag[N],g[N];
struct HHD{int l,r,id,x,y;}a[Q];
bool cmp(HHD a,HHD b)
{
if (pos[a.l]!=pos[b.l]) return a.l<b.l;
if (pos[a.l]&1) return a.r<b.r;else return a.r>b.r;
}
inline int Read()
{
char ch=getchar();for (;ch<'0'||ch>'9';ch=getchar());
int x=0;for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
inline void add(int x,int c){for (;x<=n;x+=LL(x)) f[x]+=c;}
inline int Sum(int x){int o=0;for (;x;x-=LL(x)) o+=f[x];return o;}
inline void add2(int x,int c){for (;x<=n;x+=LL(x)) g[x]+=c;}
inline int Sum2(int x){int o=0;for (;x;x-=LL(x)) o+=g[x];return o;}
int main()
{
n=Read();m=Read();
for (i=1;i<=n;i++)
data[i]=Read();
s=int(sqrt(n));
for (i=1;i<=n;i++) pos[i]=i/s+1;
for (i=1;i<=m;i++)
{
L=Read(),R=Read(),x=Read(),y=Read();
a[i].l=L;a[i].r=R;a[i].x=x;a[i].y=y;a[i].id=i;
}
sort(a+1,a+m+1,cmp);
l=1;r=1;flag[data[1]]=1;add(data[1],1);add2(data[1],1);
for (i=1;i<=m;i++)
{
while (r<a[i].r) {flag[data[++r]]++;if (flag[data[r]]==1) add(data[r],1);add2(data[r],1);}
while (l>a[i].l) {flag[data[--l]]++;if (flag[data[l]]==1) add(data[l],1);add2(data[l],1);}
while (r>a[i].r) {flag[data[r]]--;if (!flag[data[r]]) add(data[r],-1);add2(data[r],-1);r--;}
while (l<a[i].l) {flag[data[l]]--;if (!flag[data[l]]) add(data[l],-1);add2(data[l],-1);l++;}
ans2[a[i].id]=Sum(a[i].y)-Sum(a[i].x-1);
ans1[a[i].id]=Sum2(a[i].y)-Sum2(a[i].x-1);
}
for (i=1;i<=m;i++)
printf("%d %d\n",ans1[i],ans2[i]);
return 0;
}

树套树专题——bzoj 3110: [Zjoi2013] K大数查询 &amp; 3236 [Ahoi2013] 作业 题解的更多相关文章

  1. BZOJ 3110&colon; &lbrack;Zjoi2013&rsqb;K大数查询 &lbrack;树套树&rsqb;

    3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6050  Solved: 2007[Submit][Sta ...

  2. bzoj 3110&colon; &lbrack;Zjoi2013&rsqb;K大数查询 树状数组套线段树

    3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1384  Solved: 629[Submit][Stat ...

  3. BZOJ 3110&colon; &lbrack;Zjoi2013&rsqb;K大数查询&lpar; 树状数组套主席树 &rpar;

    BIT+(可持久化)权值线段树, 用到了BIT的差分技巧. 时间复杂度O(Nlog^2(N)) ---------------------------------------------------- ...

  4. BZOJ 3110&lpar;&lbrack;Zjoi2013&rsqb;K大数查询-区间第k大&lbrack;段修改&comma;在线&rsqb;-树状数组套函数式线段树&rpar;

    3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec   Memory Limit: 512 MB Submit: 418   Solved: 235 [ Submit][ ...

  5. BZOJ 3110 &lbrack;Zjoi2013&rsqb;K大数查询(整体二分)

    3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 11654  Solved: 3505[Submit][St ...

  6. BZOJ 3110 &lbrack;Zjoi2013&rsqb;K大数查询 ——树套树

    [题目分析] 外层区间线段树,内层是动态开点的权值线段树. SY神犇说树套树注重的是内外层的数据结构的选择问题,果然很重要啊. 动态开点的实现方法很好. [代码] #include <cstdi ...

  7. bzoj 3110 &lbrack;Zjoi2013&rsqb;K大数查询【树套树&vert;&vert;整体二分】

    树套树: 约等于是个暴力了.以区间线段树的方式开一棵权值线段树,在权值线段树的每一个点上以动态开点的方式开一棵区间线段树. 结果非常惨烈(时限20s) #include<iostream> ...

  8. BZOJ 3110&colon; &lbrack;Zjoi2013&rsqb;K大数查询 &lbrack;整体二分&rsqb;

    有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. N ...

  9. &lbrack;BZOJ 3110&rsqb; &lbrack;Zjoi2013&rsqb; K大数查询 【树套树】

    题目链接: BZOJ - 3110 题目分析 这道题是一道树套树的典型题目,我们使用线段树套线段树,一层是区间线段树,一层是权值线段树.一般的思路是外层用区间线段树,内层用权值线段树,但是这样貌似会很 ...

随机推荐

  1. 【java基础】java的构造函数

    java构造器用于创建类的实例,是创建对象的重要途径,因此,java类必须含有一个或一个以上的构造函数   当我们没有为类提供任何构造函数的时候,系统会自动为该类提供一个无参构造函数,当我们为类提供了 ...

  2. HDU2819-Swap-二分图匹配

    把矩阵上的1建成边,把边建成点 然后跑一个二分图匹配,就找到了主对角线的元素,之后排个序就可以了 /*------------------------------------------------- ...

  3. Dynamic CRM 2015学习笔记 系列汇总

    这里列出所有 Dynamic CRM 2015学习笔记 系列文章,方便大家查阅.有任何建议.意见.需要,欢迎大家提交评论一起讨论. 本文原文地址:Dynamic CRM 2015学习笔记 系列汇总 一 ...

  4. Maven 安装记

    java初学者 昨天通m2e插件把maven项目导入eclipse的时候各种bug,看了各家技术博客,决定安装maven好好了解下. 安装maven也是一波三折的,先是看各种安装指导,结果环境变量都没 ...

  5. &lbrack; An Ac a Day &Hat;&lowbar;&Hat; &rsqb; &lbrack;kuangbin带你飞&rsqb;专题八 生成树 UVA 10600&Tab;ACM Contest and Blackout 最小生成树&plus;次小生成树

    题意就是求最小生成树和次小生成树 #include<cstdio> #include<iostream> #include<algorithm> #include& ...

  6. WEB端线上偶现问题如何复现?

    1.抓取出现问题的日志,还原操作过程,分析 每个过程中数据是否正常?是否有重复请求 2.询问当时操作员执行了哪些操作,尽可能多的了解事发经过 3.通过查看日志,数据库等信息,找到发生问题的节点, 比如 ...

  7. object oriented programming &colon; class application

    class Thread_Sync; class Critical; class Info; class Info{Info(std::string str):m_info(str){} privat ...

  8. mongodb添加验证用户 删除用户

    1.创建用户 db.createUser( { user:<name_string>,                   #字符串 pwd:<password_string> ...

  9. 单位转换类UnitUtil2

    package com.jlb.scan.util; import java.math.BigDecimal; import java.text.DecimalFormat; import com.j ...

  10. 通过过滤器和增强request对象解决get提交请求服务器端乱码。

    1.表单用get方式提交 <%@ page language="java" contentType="text/html; charset=UTF-8" ...