\(\text{Mex}\)

题目描述

给定长度为 \(n\) 的序列,有 \(q\) 个询问 \((l,r)\),对于每个询问,你需要回答 \(\text{mex}\{a_l,a_{l+1},...,a_r\}\)

\(1\le n,q\le 2\cdot 10^5\)\(0\le a_i\le 2\cdot 10^5\)

解法

这题 \(\sf ET\) 说至少做过两次,可是我真的毫无印象… 那么只有 —— 馍馍馍 \(\sf ET\)

离线

很妙的一点是用到了 \(\rm mex\) 的删除非常简单!如果在集合中添加数则很难维护答案的变化;而删除数则只用维护删除的最小值,更大的都不可能是答案。

把所有询问塞到以左端点为下标的 vector 里。考虑先计算出左端点为 \(1\) 的所有答案,可以用 set 从右到左删除来维护,除此之外,再预处理出 \(nxt_i\) 表示在 \(i\) 之后下一个出现 \(a_i\) 的位置。

这样从 \(1\) 开始从左到右删除点,将 \(a_i\) 与右端点在 \((i,nxt_i)\) 的答案取 \(\min\) 即可。

在线

以区间为根的下标,搞 \(n\) 棵可持久化权值线段树。从左到右枚举根,在权值线段树内部维护某个权值 \(val\) 当前最大的下标。对于查询 \((l,r)\),在 \(r\) 号根中进行二分,如果左儿子所有权值最大下标的 \(\min<l\),说明左儿子中存在答案,往左儿子递归;反之往右儿子递归,因为值域大小 \(2\cdot 10^5+1\) 大于序列长度 \(2\cdot 10^5\),所以不用考虑超出的问题。

\(\text{CF1422F Boring Queries}\)

解法

首先比较容易想到的是,将小于 \(\sqrt {a_i}\) 的质数暴力用线段树维护指数 \(\max\),大概是 \(\mathcal O(86\cdot n\log n)\)

关键是处理大于 \(\sqrt {a_i}\) 的质数,也就是询问区间不同数字的积。对于此类询问我们一般会搞一个 \(pre_i/nxt_i\) 来表示在 \(i\) 之前或之后第一个权值为 \(a_i\) 的下标,从而去重。下面讲的三种方案也是基于此来解决的。

我会主席树!

对于询问 \((l,r)\),如果 \(i\in[l,r]\)\(pre_i<l\),就将 \(a_i\) 加入贡献,否则不予处理。一个惊喜的发现是,对于 \(j\in [0,n]\),只会有一个 \(i\) 满足 \(pre_i=j\),也即如果以 \(j\) 为下标可以建立主席树。

具体来说,用 \(rt_i\) 维护所有 \(pre_j\le i\)\(j\),权值线段树表示下标区间。这么一想好像也可以用树套树?最后查询就只用在 \(rt_{l-1}\) 中找 \([l,r]\) 即可。

另:如果想用 \(\rm st\) 表维护小于 \(\sqrt {a_i}\) 的质数的指数,最好把它开成 char 类型的。

时间复杂度 \(\mathcal O(n\log n+86\cdot n\log n)\),空间复杂度大概在 \(\text S(n\log n+86\cdot 4n)\)

我会分块!

预处理整块之间的 \(\rm lcm\)。对于散块,预处理 \(pre,nxt\),当落在区间之外才计算贡献。

时间复杂度 \(\mathcal O(n\sqrt n+86\cdot n\log n)\),空间复杂度 \(\text S(n+86\cdot 4n)\)

我会归并!

\((pre_i,a_i)\) 放在线段树的叶子节点上,再按 \(pre_i\) 的大小往上归并。因为一个点只会被归并 \(\log n\) 次,所以复杂度有保障。查询的时候,将 \((l,r)\) 划分成线段树上的节点,在节点的归并序列中二分最后一个 \(pre<l\) 的位置 \(p\),这个节点的贡献就是到 \(p\) 的前缀积。

时间复杂度 \(\mathcal O(q \log^2 n+86\cdot n\log n)\),空间复杂度 \(\text S(n\log n+86\cdot 4n)\)

代码

代码写的是主席树,是我觉得最经济的方案。

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <iostream>
using namespace std;

const int mod=1e9+7,maxn=1e5+5,lim=2e5;

int n,t[maxn<<2][86],a[maxn];
int p[100],pc=-1,rt[maxn],idx;
int nxt[maxn],val[lim+5],Q[86];
int P[86][19];
bool vis[500];
struct node {
	int ls,rs,mul;
} s[maxn*90];

void build(int o,int l,int r) {
	if(l==r) {
		a[l]=read(9);
		for(int i=0;i<=pc;++i)
			if(a[l]%p[i]==0) {
				while(a[l]%p[i]==0)
					++t[o][i],
					a[l]/=p[i];
			}
		return;
	}
	int mid=l+r>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
	for(int i=0;i<=pc;++i)
		t[o][i]=max(t[o<<1][i],t[o<<1|1][i]);
}

void Query(int o,int l,int r,int L,int R) {
	if(l>R or r<L) return;
	if(l>=L and r<=R) {
		for(int i=0;i<=pc;++i)
			Q[i]=max(Q[i],t[o][i]);
		return;
	}
	int mid=l+r>>1;
	Query(o<<1,l,mid,L,R);
	Query(o<<1|1,mid+1,r,L,R); 
}

void sieve() {
	for(int i=2;i<=447;++i) {
		if(!vis[i]) p[++pc]=i;
		for(int j=0;j<=pc and i*p[j]<=447;++j) {
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}

void Build(int &o,int l,int r) {
	s[o=++idx].mul=1;
	if(l==r) return;
	int mid=l+r>>1;
	Build(s[o].ls,l,mid);
	Build(s[o].rs,mid+1,r); 
}

void ins(int o,int l,int r,int p,int k) {
	if(l==r) return (void)(s[o].mul=k);
	int mid=l+r>>1;
	if(p<=mid) ins(s[o].ls,l,mid,p,k);
	else ins(s[o].rs,mid+1,r,p,k);
	s[o].mul=1ll*s[s[o].ls].mul*s[s[o].rs].mul%mod;
}

void modify(int &o,int fa,int l,int r,int p,int k) {
	o=++idx; s[o]=s[fa];
	if(l==r) return (void)(s[o].mul=k);
	int mid=l+r>>1;
	if(p<=mid) modify(s[o].ls,s[fa].ls,l,mid,p,k);
	else modify(s[o].rs,s[fa].rs,mid+1,r,p,k);
	s[o].mul=1ll*s[s[o].ls].mul*s[s[o].rs].mul%mod;
}

int ask(int o,int l,int r,int L,int R) {
	if(l>R or r<L) return 1;
	if(l>=L and r<=R) return s[o].mul;
	int mid=l+r>>1;
	return 1ll*ask(s[o].ls,l,mid,L,R)*
			ask(s[o].rs,mid+1,r,L,R)%mod;
}

void init_SgTree() {
	Build(rt[0],1,n);
	for(int i=1;i<=n;++i) {
		if(!val[a[i]])
			ins(rt[0],1,n,i,a[i]);
		else nxt[val[a[i]]]=i;
		val[a[i]]=i;
	}
	for(int i=1;i<=n;++i)
		if(nxt[i])
			modify(rt[i],rt[i-1],1,n,nxt[i],a[i]);
		else rt[i]=rt[i-1];
}

void init_pow() {
	for(int i=0;i<=pc;++i) {
		P[i][0]=1;
		for(int j=1;j<=18;++j)
			P[i][j]=1ll*P[i][j-1]*p[i]%mod;
	}
}

int main() {
	sieve();
	n=read(9);
	build(1,1,n);
	int lst=0;
	init_pow();
	init_SgTree();
	for(int q=read(9);q;--q) {
		int l,r;
		l=(read(9)+lst)%n+1;
		r=(read(9)+lst)%n+1;
		if(l>r) swap(l,r);
		Query(1,1,n,l,r);
		int ans=1;
		for(int i=0;i<=pc;++i)
			ans=1ll*ans*P[i][Q[i]]%mod,
			Q[i]=0;
		ans=1ll*ans*ask(rt[l-1],1,n,l,r)%mod;
		print(lst=ans,'\n');
	}
	return 0;
}

\(\text{Colorful Squares}\)

解法

首先二分长度 \(L\)。从小到大枚举 \(x\),考虑 \([\max\{1,x-L\},x]\) 中的点,如果满足 \(y\) 坐标存在一段区间,里面包含的颜色种数 \(\ge k\)\(L\) 就是可行的。

一段 \(y\) 坐标中不同的颜色数可以用线段树维护,也即区间加,区间 \(\max\)。关键是如何计算一个点统治的区间。可以对每种颜色开一个 multiset,查询离当前点最近的两个 \(y\) 值的统治区间,不让它们产生交即可。具体实现可以看代码。

总时间复杂度 \(\mathcal O(n\log^2 n)\)。这里视 \(n\) 与值域相等。

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <bits/stdc++.h>
using namespace std;

const int maxs=1e5+5,maxn=250000;

int n,k;
multiset <int> s[maxn+5];
multiset <int> :: iterator it;
struct SgTree {
	struct node {
		int la,v;
	} t[maxn+5<<2];
	
	void build(int o,int l,int r) {
		t[o].la=t[o].v=0;
		if(l==r) return;
		int mid=l+r>>1;
		build(o<<1,l,mid);
		build(o<<1|1,mid+1,r);
	}
	
	void pushDown(int o) {
		if(!t[o].la) return;
		t[o<<1].v+=t[o].la;
		t[o<<1|1].v+=t[o].la;
		t[o<<1].la+=t[o].la;
		t[o<<1|1].la+=t[o].la;
		t[o].la=0;
	}
	
	void modify(int o,int l,int r,int L,int R,int k) {
		if(L>R or l>R or r<L) return;
		if(l>=L and r<=R) {
			t[o].v+=k;
			t[o].la+=k;
			return;
		}
		pushDown(o);
		int mid=l+r>>1;
		modify(o<<1,l,mid,L,R,k);
		modify(o<<1|1,mid+1,r,L,R,k);
		t[o].v=max(t[o<<1].v,t[o<<1|1].v);
	}
} T;
struct pii {
	int y,c;
};
vector <pii> Y[maxn+5];

bool ok(int mid) {
	T.build(1,1,maxn);
	for(int i=1;i<=k;++i)
		s[i].clear();
	for(int i=1;i<=maxn;++i) {
		for(pii j:Y[i]) {
			it=s[j.c].find(j.y);
			if(it!=s[j.c].end()) {
				s[j.c].insert(j.y);
				continue;
			}
			s[j.c].insert(j.y);
			it=s[j.c].lower_bound(j.y);
			int l,r;
			if(it==s[j.c].begin())
				l=max(1,j.y-mid);
			else {
				--it;
				l=max(j.y-mid,*it+1);
			}
			it=s[j.c].upper_bound(j.y);
			if(it==s[j.c].end()) r=j.y;
			else r=min(j.y,*it-mid-1);
			T.modify(1,1,maxn,l,r,1);
		}
		if(i-mid-1>=1) {
			for(pii j:Y[i-mid-1]) {
				s[j.c].erase(s[j.c].find(j.y));
				it=s[j.c].find(j.y);
				if(it!=s[j.c].end()) 
					continue;
				it=s[j.c].lower_bound(j.y);
				int l,r;
				if(it==s[j.c].begin())
					l=max(1,j.y-mid);
				else {
					--it;
					l=max(j.y-mid,*it+1);
				}
				it=s[j.c].upper_bound(j.y);
				if(it==s[j.c].end()) r=j.y;
				else r=min(j.y,*it-mid-1);
				T.modify(1,1,maxn,l,r,-1);
			}
		}
		if(T.t[1].v>=k) return 1;
	}
	return 0;
}

int main() {
	n=read(9),k=read(9);
	for(int i=1;i<=n;++i) {
		int x,y,z;
		x=read(9),y=read(9),z=read(9);
		Y[x].push_back((pii){y,z});
	}
	int l=0,r=maxn,mid;
	while(l<r) {
		int mid=l+r>>1;
		if(ok(mid)) r=mid;
		else l=mid+1;
	}
	print(l,'\n');
	return 0;
}
posted on 2021-08-31 18:09  Oxide  阅读(44)  评论(0编辑  收藏  举报