「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\times10^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=\lceil\log_2n\rceil\),\(Q=\lceil\log_2q\rceil\).

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

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

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

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

所以总操作数上限为 \(2^{N+\frac{Q}{2}+1}\)\(n\sqrt{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\n",cnt);
	REP(i,n+1,cnt)
	{
		printf("%d %d\n",use[i].left,use[i].right);
	}
	REP(i,1,q)
	{
		printf("%d ",answer[i]);
	}
	return 0;
}
posted @ 2020-07-28 09:33  SxyLimit  阅读(197)  评论(1编辑  收藏  举报