树套树专题——bzoj 3110: [Zjoi2013] K大数查询 & 3236 [Ahoi2013] 作业 题解「建议收藏」

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

大家好,又见面了,我是全栈君。

【原题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;
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/115464.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • lammps教程:薄膜渗透模拟(3)–不同孔隙率对过滤效果的影响

    lammps教程:薄膜渗透模拟(3)–不同孔隙率对过滤效果的影响本文是薄膜渗透过滤的最后一篇文章:不同孔隙率薄膜建模。孔隙或空位缺陷的建模原理比较简单:删除一定数量的原子就可以。lammps自带delete_atoms可以随机删除一定比例的原子,如果对孔隙或空位的形状、尺寸等有特殊需求,需要用编程的方法删除原子。delete_atomsporosity命令可随时产生设定比例的原子,如删除50%的原子:delete_atomsporositymembrane0.5482793membrane为原子组0.5为删除原子的比例482793为随机数种子

    2025年8月31日
    3
  • 黑苹果怎么安装clover(clover引导教程)

    黑苹果安装教程多逛逛论坛,多攒攒人品,相信人品加技术再有点经验,安装黑苹果并不难(大神说的。。。。)

    2022年4月17日
    58
  • 360奇安信天擎卸载不干净_奇安信Ateam

    360奇安信天擎卸载不干净_奇安信Ateam360天擎是什么具体可以参考:百度:360天擎总之,一些单位里为了安全防护,会在单位的电脑上安装360天擎,如果你想用自己的笔记本电脑连上单位的网络的话,也会要求你安装。不安装就连不上网络,但安装之后,卸载又是一个很大的难题。用什么卸载其实网上会有很多强力卸载软件,甚至有些会强力到可以卸载系统文件,这类软件不建议使用(如果用的明白,请自便)。无效方式如,腾讯管家自带的软件管理有效方式如,联想电脑管家,和微软自带的卸载,如下图:点击右键->卸载->完成?不不不,这

    2022年9月25日
    3
  • android+号码归属地数据库,Android手机号码归属地的查询「建议收藏」

    android+号码归属地数据库,Android手机号码归属地的查询「建议收藏」一个简单的Demo,从聚合数据申请手机号码归属地数据接口;在EditText中输入待查询号码,获取号码后在子线程中使用HttpUrlconnection获取JSON数据,之后进行解析;数据获取完成后,在主线程中更新UI,显示获取的号码归属地信息。布局文件android:layout_width=”match_parent”android:layout_height=”match_parent”an…

    2022年7月22日
    13
  • java应用被阻止_怎样解决运行java提示应用程序已安全设置被阻止[通俗易懂]

    java应用被阻止_怎样解决运行java提示应用程序已安全设置被阻止[通俗易懂]Win7系统运行java时出现提示应用程序已安全设置被阻止,这样就导致运行java失败,那么怎样解决运行java提示应用程序已安全设置被阻止呢?下面跟着学习啦小编来一起了解下吧。解决运行java提示应用程序已安全设置被阻止方法1、点击:开始-控制面板,选择查看方式为:大图标或小图标;2、双击java,选择“安全”,把“安全级别”降至“中”,点击“确定”;3、重启浏览器,运行java,在弹出的对话框…

    2022年7月7日
    20
  • LeetCode——Add Binary

    LeetCode——Add Binary

    2022年1月4日
    67

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号