zoukankan      html  css  js  c++  java
  • 「CF1375H Set Merging」

    题目大意

    给出一个长度为 (n) 的序列 (a).开始时有 (n) 个集合 ({a_1},{a_2},dots,{a_{n-1}},{a_n}).每次可以合并两个集合 (S,T),其中 (S,T) 要满足 (S) 集合中的最大值小于 (T) 集合中的最小值.接下来给出 (q) 次查询,每次查询给出 (l_i,r_i),需要在 (2.2 imes10^6) 操作内制作出所有的集合 ({a_{l_i},a_{l_i+1},dots,a_{r_i-1},a_{r_i}}).需要给出操作以及每个查询的集合的编号.

    分析

    考虑合并两个集合条件为一集合的最大值小于另一集合的最小值.查询的是取药组合出开始给出的序列的一个子串.所以最简单粗暴的方法就是找到子串内的数排序后逐个合并,但这显然是不行的.但这也提示了单次查询的操作上限必然是 (n).所以需要考虑如何利用一些以前利用过的集合.考虑开一个权值线段树来维护,对于每个节点上需要维护当前节点内的数在数列上所有出现的位置.

    那么暴力查询的代码就如下:

    int Query(int now_left,int now_right,int now=1)
    {
        //num 中记录的是当前范围内的数的所有出现位置(单调递增)
    	int left=lower_bound(sgt[now].num.begin(),sgt[now].num.end(),now_left)-sgt[now].num.begin();
    	int right=upper_bound(sgt[now].num.begin(),sgt[now].num.end(),now_right)-sgt[now].num.begin()-1;
    	//找到当前查询的区间中在这个范围内的数最左边和最右边的位置
        if(right<left)//如果 right<left 显然不存在这个范围内的数
    	{
    		return 0;
    	}
    	if(left==right)//如果只有一个数那么可以直接用开始给出的集合
    	{
    		return sgt[now].num[left];
    	}
    	int lson=Query(NOW,LSON),rson=Query(NOW,RSON);//查找子树
    	if(!lson||!rson)//如果左子树或右子树为空那么当前层就可以不用合并
    	{
    		return lson|rson;
    	}
        //use 表示操作
    	use[++cnt]=Range(lson,rson);//否则需要合并
    	return cnt;
    }
    

    这样就可以做到单次查询操作数小于 (n) 了.

    考虑如何优化这个东西.

    考虑对于每个线段树上的节点只会有 (len^2)((len) 是所对应的值域的范围)种本质不同的查询(即上面代码中的 left,right 的不同种类).那么可以将 left,right 扔到 map 中,那么对于每个节点所产生的操作上限就是 (len^2) 级别了.

    代码如下:

    int Query(int now_left,int now_right,int now=1)
    {
    	int left=lower_bound(sgt[now].num.begin(),sgt[now].num.end(),now_left)-sgt[now].num.begin();
    	int right=upper_bound(sgt[now].num.begin(),sgt[now].num.end(),now_right)-sgt[now].num.begin()-1;
    	if(right<left)
    	{
    		return 0;
    	}
    	if(left==right)
    	{
    		return sgt[now].num[left];
    	}
    	int p=sgt[now].Hash[make_pair(left,right)];
    	if(p)//如果存在过就直接返回
    	{
    		return p;
    	}
    	int lson=Query(NOW,LSON),rson=Query(NOW,RSON);
    	if(!lson||!rson)
    	{
    		return sgt[now].Hash[make_pair(left,right)]=lson|rson;//每次查询完就扔到 map 里
    	}
    	use[++cnt]=Range(lson,rson);
    	return sgt[now].Hash[make_pair(left,right)]=cnt;
    }
    

    然后就会惊讶的发现可以过了.

    复杂度分析(感谢 (color{black}{sf A}color{red}{sf{utumnKite}}) 提供证明思路):

    (N=lceillog_2n ceil),(Q=lceillog_2q ceil).

    那么对于深度大于 (frac{Q}{2}) 层的操作数上限为(对于每个节点的 (len^2) 种情况都跑满)

    [sum_{i=1}^{frac{Q}{2}}(2^{N-i}cdotfrac{2^{i^2}}{2})=sum_{i=1}^{frac{Q}{2}}2^{N+i-1}leq 2^{N+frac{Q}{2}} ]

    对于深度小于等于 (frac{Q}{2}) 层的操作数上限为(每次操作都将所有节点跑到)

    [q imes2^{N-frac{Q}{2}}leq 2^{N+frac{Q}{2}} ]

    所以总操作数上限为 (2^{N+frac{Q}{2}+1})(nsqrt{q}) 同阶.

    代码

    #include<bits/stdc++.h>
    #define REP(i,first,last) for(int i=first;i<=last;++i)
    #define DOW(i,first,last) for(int i=first;i>=last;--i)
    using namespace std;
    const int MAXN=1e5+5;
    const int MAXQ=2e6+2e5+5;
    int n,q;
    int arr[MAXN];
    int app[MAXN];
    struct Range//记录操作
    {
    	int left,right;
    	Range(int l=0,int r=0)
    	{
    		left=l;
    		right=r;
    	}
    }use[MAXQ];
    int answer[MAXN];//记录答案
    int cnt=0;
    struct SegmentTree
    {
    	vector<int>num;
    	map<pair<int,int>,int>Hash;
    }sgt[MAXN*4];
    #define LSON (now<<1)
    #define RSON (now<<1|1)
    #define MIDDLE ((left+right)>>1)
    #define LEFT LSON,left,MIDDLE
    #define RIGHT RSON,MIDDLE+1,right
    #define NOW now_left,now_right
    void Build(int now=1,int left=1,int right=n)//建树部分
    {
    	REP(i,left,right)//将在当前范围内的数扔到节点里面
    	{
    		sgt[now].num.push_back(app[i]);
    	}
    	sort(sgt[now].num.begin(),sgt[now].num.end());//按出现位置排序
    	if(left==right)
    	{
    		return;
    	}
    	Build(LEFT);
    	Build(RIGHT);
    }
    int Query(int now_left,int now_right,int now=1)//同上的记忆化查询
    {
    	int left=lower_bound(sgt[now].num.begin(),sgt[now].num.end(),now_left)-sgt[now].num.begin();
    	int right=upper_bound(sgt[now].num.begin(),sgt[now].num.end(),now_right)-sgt[now].num.begin()-1;
    	if(right<left)
    	{
    		return 0;
    	}
    	if(left==right)
    	{
    		return sgt[now].num[left];
    	}
    	int p=sgt[now].Hash[make_pair(left,right)];
    	if(p)
    	{
    		return p;
    	}
    	int lson=Query(NOW,LSON),rson=Query(NOW,RSON);
    	if(!lson||!rson)
    	{
    		return sgt[now].Hash[make_pair(left,right)]=lson|rson;
    	}
    	use[++cnt]=Range(lson,rson);
    	return sgt[now].Hash[make_pair(left,right)]=cnt;
    }
    #undef LSON
    #undef RSON
    #undef MIDDLE
    #undef LEFT
    #undef RIGHT
    #undef NOW
    int main()
    {
    	scanf("%d%d",&n,&q);
    	cnt=n;
    	REP(i,1,n)
    	{
    		scanf("%d",&arr[i]);
    		app[arr[i]]=i;
    	}
    	Build();
    	int left,right;
    	REP(i,1,q)
    	{
    		scanf("%d%d",&left,&right);
    		answer[i]=Query(left,right);
    	}
    	printf("%d
    ",cnt);
    	REP(i,n+1,cnt)
    	{
    		printf("%d %d
    ",use[i].left,use[i].right);
    	}
    	REP(i,1,q)
    	{
    		printf("%d ",answer[i]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    第一周实习工作总结
    service---七月十九号实验
    安卓常用链接
    Activity + 基础UI
    七月十四日安卓学习笔记
    安卓组件学习笔记
    剑指:数组中出现次数超过一半的数字
    剑指:二叉搜索树与双向链表
    剑指:二叉树中和为某一值的路径
    剑指:二叉搜索树的后序遍历序列
  • 原文地址:https://www.cnblogs.com/Sxy_Limit/p/13389277.html
Copyright © 2011-2022 走看看