集合求交,51nod1818,根号分治

本文介绍了一种利用根号分治策略优化数据结构的方法,特别适用于元素数量不超过M的情况。通过维护线段树和树状数组,实现对特殊集合的高效查询和更新,降低了时间复杂度。

正题

      Portal

      这题发现总的元素数量不超过M,所以我们可以对一个集合内的元素数量来根号分治。

      当询问的|S_p|<T时,暴力维护每一个权值以位置为关键字的线段树(动态开点),这部分的时间复杂度是O(m \log_2 n+mT\log_2 n )

      当询问的|S_p|>=T时,我们对于这些特殊的集合维护一个以位置为关键字的树状数组,每个位置的权值就是该集合与特殊集合的交集,更新显然,每次查询直接差分前缀和即可。这部分的时间复杂度为O(\frac{m}{T}m\log_2n+m\log_2n)

      这两个都是最坏时间复杂度,当T=\sqrt m时,有最小时间复杂度。

      但是为了避免麻烦,我们可以直接设为T=\left \lceil \frac{m}{1000} \right \rceil。就保证了特殊集合最多只有1000个。

      发现空间上还是爆炸,直接考虑把空间开小一点,或者选择vector,因为实际上的特殊集合并没有那么多。

      反正我空间是卡过去的,时间上没有问题。

#include<cmath>
#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define lowbit(x) (x&(-x))
using namespace std;

const int N=40010,M=200010,A=1000000;
int n,m,bound,tot,num;
vector<int> S[N],V[A+1];
bool tf[501][A+1];
int root[A+1],id[N];
int t[M*16],ls[M*16],rs[M*16];
int sum[501][N];

void insert(int&now,int d,int l,int r){
	if(now==0) now=++num;
	t[now]++;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(d<=mid) insert(ls[now],d,l,mid);
	else insert(rs[now],d,mid+1,r);
}

void add(int*now,int x,int t){
	while(x<=n){
		now[x]+=t;
		x+=lowbit(x);
	}
	return ;
}

int get_tot(int now,int x,int y,int l,int r){
	if(x==l && y==r) return t[now];
	int mid=(l+r)/2;
	if(y<=mid) return get_tot(ls[now],x,y,l,mid);
	else if(mid<x) return get_tot(rs[now],x,y,mid+1,r);
	else return get_tot(ls[now],x,mid,l,mid)+get_tot(rs[now],mid+1,y,mid+1,r);
}

int get_sum(int*now,int x){
	long long tot=0;
	while(x){
		tot+=now[x];
		x-=lowbit(x);
	}
	return tot;
}

int main(){
	char c[2];
	int x,y,l;
	scanf("%d %d",&n,&m);bound=ceil(m/1000.0);
	while(m--){
		scanf("%s %d %d",c,&x,&y);
		if(c[0]=='I'){
			S[x].push_back(y);
			if(S[x].size()==bound){
				id[x]=++tot;
				for(int j=0;j<S[x].size();j++) tf[id[x]][S[x][j]]=true;
				for(int j=1;j<=n;j++) {
					int t=0;
					for(int k=0;k<S[j].size();k++) if(tf[id[x]][S[j][k]]) t+=S[j][k];
					if(t){
						add(sum[id[x]],j,t);
						if(j!=x && id[j] && tf[id[j]][y]) add(sum[id[j]],x,y);
					}
				}
			}
			else if(S[x].size()>bound){
				tf[id[x]][y]=true;
				for(int j=0;j<V[y].size();j++){
					add(sum[id[x]],V[y][j],y);
					if(id[V[y][j]]) add(sum[id[V[y][j]]],x,y);
				}
				add(sum[id[x]],x,y);
			}
			else if(S[x].size()<bound) for(int j=1;j<=tot;j++) if(tf[j][y]) add(sum[j],x,y);
			insert(root[y],x,1,n);V[y].push_back(x);
		}
		else{
			scanf("%d",&l);
			if(S[x].size()<bound){
				int tot=0;
				for(int j=0;j<S[x].size();j++)
					tot+=get_tot(root[S[x][j]],y,l,1,n)*S[x][j];
				printf("%d\n",tot);
			}
			else if(S[x].size()>=bound) printf("%d\n",get_sum(sum[id[x]],l)-get_sum(sum[id[x]],y-1));
		}
	}
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值