[CF1375H]Set Merging

Set Merging

题解

看到这道题,应该是很容易想到去分块的。

由于它要求合并两个集合时,一个的最小值必须是小于另一个的最大值,我们必须先对值域进行分块。
将任意一个block内所有区间都先更新出来,具体的合并操作可以在线段树上实现,我们可以在建树时就将区间的合并给更新出来。
之后每次查询的时候就只需要将各个块上的区间按序合并就可以了,容易证明,每次查询只需要合并 n \sqrt{n} n 个块内的区间,由于 q = 2 16 , n = 2 12 q=2^{16},n=2^{12} q=216,n=212而又有重复合并的,总次数约为 n q n\sqrt{q} nq
总的合并次数大约为 2 n q 2n\sqrt{q} 2nq ,可以过。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define MAXN (1<<12)+5
#define MAXM (1<<22)+5
#define MAXD (1<<18)+5
typedef long long LL;
const int INF=0x7f7f7f7f;
const int n1=1<<8;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
} 
int n,q,tot,a[MAXN],b[MAXN],t[MAXN],ans[MAXD]; 
pii answ[MAXM];
int merge(int x,int y){if(!x||!y)return x+y;answ[tot]=make_pair(x,y);return ++tot;}
struct Tree{
	vector<int> val;vector<vector<int> > id;
	Tree(){}
	Tree(int x){init(1);val[0]=b[x];id[0][0]=b[x]+1;}
	void init(int x){val.resize(x);id.resize(x);for(int i=0;i<x;i++)id[i].resize(x-i);}
	int ask(int l,int r)const{
		if(r<val.front()||l>val.back())return 0;
		l=lower_bound(val.begin(),val.end(),l)-val.begin();
		r=upper_bound(val.begin(),val.end(),r)-val.begin()-1;
		return l>r?0:id[l][r-l];
	}
	Tree update(const Tree &x,const Tree &y){
		init(x.val.size()+y.val.size());
		merge(x.val.begin(),x.val.end(),y.val.begin(),y.val.end(),val.begin());
		int siz=val.size();
		for(int i=0;i<siz;i++)
			for(int j=i;j<siz;j++)
				id[i][j-i]=merge(x.ask(val[i],val[j]),y.ask(val[i],val[j]));
		return *this;
	}
}T[MAXN/n1];
Tree solve(int l,int r){
	//printf("solve%d %d\n",l,r);
	if(l==r)return Tree(l);int mid=l+r>>1;Tree res;
	return res.update(solve(l,mid),solve(mid+1,r));
}
signed main(){
	read(n);read(q);tot=n--;
	for(int i=0;i<=n;i++)read(a[i]),a[i]--,b[a[i]]=i;
	for(int i=0;i<=n/n1;i++)T[i]=solve(i*n1,min(n,(i+1)*n1-1));
	for(int i=1;i<=q;i++){
		int l,r;read(l);read(r);l--;r--;
		for(int j=0;j<=n/n1;j++)ans[i]=merge(ans[i],T[j].ask(l,r));
	}
	printf("%d\n",tot);
	for(int i=n+1;i<tot;i++)printf("%d %d\n",answ[i].first,answ[i].second);
	for(int i=1;i<=q;i++)printf("%d ",ans[i]);puts("");
	return 0;
}

谢谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值