数据结构做题记录

\(\text{Conventions}\)

  • 提到二进制的第几位时,全部为从右往左 \(0\text{-index}\)
  • 题目强制在线要求解码时,解码过程不会赘述,请在原题面查看。
  • 在题意中提到有根树时,若无特殊说明,默认以 \(1\) 为根。

\(\text{Problems}\)

\(\text{Problem}1\text{ CF}878\text{D (}2953\text{)}\)

一开始有 \(k\) 个长度为 \(n\) 的非负整数序列 \(A_1,A_2,\cdots,A_k\)

接下来有 \(q\) 次操作,有以下 \(3\) 种类型(设每次操作前有 \(tot\) 个序列):

  1. 给定 \(x\)\(y\),执行 \(tot\leftarrow tot+1,A_{tot,i}\leftarrow \max\{A_{x,i},A_{y,i}\}\)
  2. 给定 \(x\)\(y\),执行 \(tot\leftarrow tot+1,A_{tot,i}\leftarrow \min\{A_{x,i},A_{y,i}\}\)
  3. 给定 \(x\)\(y\),询问 \(A_{x,y}\)

保证 \(1\le n,q\le10^5,1\le k\textcolor{red}{\le12},0\le A_{x,y}\le10^9\)
\(\text{Time Limit }4\text{s},\text{Memory Limit }1024\text{MB}\)

$\text{Hint}$

考虑值域为 \(\{0,1\}\) 的情况,可以发现本质不同的列至多只有 \(2^k\) 个。

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)


考虑值域为 \(\{0,1\}\) 怎么做。

我们考虑构造两个数组,一个名为 \(check\),一个名为 \(S\)
我们希望 \(A_{x,y}\)\(1\),当且仅当 \(S_{x,check_{y}}\)\(1\)

考虑这么构造 \(check_y\)\(check_y\) 二进制下第 \(x-1\) 位为 \(A_{x,y}\)
若第 \(y_1\) 列和第 \(y_2\) 列完全相同,则 \(check_{y_1}=check_{y_2}\)
这样是满足我们的要求的,因为若两列本质相同,无论对它们怎么操作仍然相同。

由于我们至多维护 \(2^k\) 个本质不同的列,所以我们这么构造 \(S_x\):当且仅当 \(T\) 在二进制下第 \(x-1\) 位为 \(1\)\(S_{x,T}\)\(1\)

可以发现,这样构造对于初始 \(k\) 个序列是满足 \(\text{check}\) 的需求的。

考虑如何新建序列。

对于取 \(\max\),可以发现把 \(S_x\)\(S_y\) 全部取 \(\max\) 即可满足要求;取 \(\min\) 也是同理。
观察到当值域为 \(\{0,1\}\)\(\max\) 相当于 \(\operatorname{or}\)\(\min\) 相当于 \(\operatorname{and}\)
所以这个过程可以 \(\text{bitset}\) 优化。


现在考虑没有值域为 \(\{0,1\}\) 的限制怎么做。

可以发现,按列离散化把值域变为 \([0,k)\) 不影响答案,因此现在值域就变为 \([0,k)\)
若我们把大于 \(val\) 的值设为 \(1\),小于等于 \(val\) 的值设为 \(0\),则上述做法可以判断 \(A_{x,y}\) 是否大于 \(val\)
所以可以把原问题转化成 \(k\) 个子问题,第 \(0\le i<k\) 个子问题形如判断 \(A_{x,y}\) 是否大于 \(i\)

时间复杂度 \(O(nk^2+k\cdot2^k)-O(\dfrac{q2^k}{w})-O(qk)\),空间复杂度 \(O(nk+\dfrac{(q+k)2^k}{w})\)

$\text{Solution}$

代码中全部为 \(0\text{-index}\)

#define forUP(i,a,b) for(int i=(a);i<(b);++i)
constexpr int N=1e5+10,K=12;
int n,k,q,op,x,y,A[K][N];
int tot,lsh[N][K],check[N*K];bitset<1<<K> S[N+K];
void solve(){
	cin>>n>>k>>q;tot=k;
	forUP(i,0,k)forUP(j,0,n)cin>>A[i][j],lsh[j][i]=A[i][j];
	forUP(j,0,n){
		sort(lsh[j],lsh[j]+k);
		forUP(i,0,k){
			A[i][j]=lower_bound(lsh[j],lsh[j]+k,A[i][j])-lsh[j];
			forUP(x,0,A[i][j])check[j*k+x]|=1<<i;
		}
	}
	forUP(i,0,k)forUP(T,0,1<<k)S[i][T]=T>>i&1;
	while(q--){
		cin>>op>>x>>y,--x,--y;
		if(op==1)S[tot++]=S[x]|S[y];
		if(op==2)S[tot++]=S[x]&S[y];
		if(op==3){
			int ans=0;for(;S[x][check[y*k+ans]];++ans);
			cout<<lsh[y][ans]<<'\n';
		}
	}
}

\(\text{Problem}2\text{ CF}455\text{D (}2793\text{)}\)

给定一个长度为 \(n\) 的正整数序列 \(A\)
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(l,r\),将 \([l,r]\) 向右循环平移 \(1\) 位。
  2. 给定 \(l,r,val\),询问 \(\displaystyle\sum_{i=l}^r[A_i=val]\)

强制在线。
保证 \(1\le n,q\le10^5,1\le A_i,val\le n,1\le l\le r\le n\)
\(\text{Time Limit }4\text{s},\text{Memory Limit }256\text{MB}\)

$\text{Hint}$

考虑对序列分块。

$\text{Tutorial}$

发现平衡树不太好维护要查询的东西,考虑对序列分块。

设块长为 \(S\),考虑对每个块都开一个桶和双端队列。
查询是简单的,散块直接暴力遍历双端队列统计,整块直接调用桶的信息。

接下来具体讲讲如何修改。
\(l\)\(r\) 在同一块内,直接暴力循环位移;
否则暴力位移处理散块,用 push_frontpop_back 维护中间整块的双端队列,同时维护桶的信息。

单次修改和查询时间复杂度都为 \(O(S+\dfrac{n}{S})\),取 \(S=\sqrt{n}\) 可以得到最优复杂度 \(O(\sqrt{n})\)

时间复杂度为 \(O(n)-O(q\sqrt{n})-O(q\sqrt{n})\),空间复杂度为 \(O(n\sqrt{n})\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
#define pushb push_back
#define popb pop_back
#define pushf push_front
constexpr int N=1e5+10,B=400,TOT=250+10;
int n,q,A[N],op,l,r,val,ans;
int get(int x){return (x+ans-1)%n+1;}
int blng[N],L[TOT],R[TOT],cnt[TOT][N];deque<int> dq[TOT];
void build(){
	L[0]=1-B;forUp(i,1,250){
		L[i]=L[i-1]+B,R[i]=R[i-1]+B;
		forUp(pos,L[i],R[i])blng[pos]=i;
	}
	R[blng[n]]=n;
	forUp(pos,1,n){
		++cnt[blng[pos]][A[pos]];
		dq[blng[pos]].pushb(A[pos]);
	}
}
void update(int l,int r){
	int lb=blng[l],rb=blng[r],tmp;
	if(lb==rb){
		tmp=dq[lb][r-L[lb]];
		forDown(pos,r,l+1)dq[lb][pos-L[lb]]=dq[lb][pos-1-L[lb]];
		dq[lb][l-L[lb]]=tmp;
	}
	else{
		tmp=dq[lb][R[lb]-L[lb]];
		--cnt[lb][tmp],++cnt[lb][dq[rb][r-L[rb]]];
		forDown(pos,R[lb],l+1)dq[lb][pos-L[lb]]=dq[lb][pos-1-L[lb]];
		dq[lb][l-L[lb]]=dq[rb][r-L[rb]];
		forUp(i,lb+1,rb-1){
			--cnt[i][dq[i][R[i]-L[i]]],++cnt[i][tmp];
			dq[i].pushf(tmp),tmp=dq[i][R[i]-L[i]+1],dq[i].popb();
		}
		--cnt[rb][dq[rb][r-L[rb]]],++cnt[rb][tmp];
		forDown(pos,r,L[rb]+1)dq[rb][pos-L[rb]]=dq[rb][pos-1-L[rb]];
		dq[rb][0]=tmp;
	}
}
int ask(int l,int r,int val){
	int lb=blng[l],rb=blng[r],ans=0;
	if(lb==rb)forUp(pos,l,r)ans+=dq[lb][pos-L[lb]]==val;
	else{
		forUp(pos,l,R[lb])ans+=dq[lb][pos-L[lb]]==val;
		forUp(pos,L[rb],r)ans+=dq[rb][pos-L[rb]]==val;
		forUp(i,lb+1,rb-1)ans+=cnt[i][val];
	}
	return ans;
}
void solve(){
	cin>>n;
	forUp(i,1,n)cin>>A[i];
	build();
	cin>>q;
	while(q--){
		cin>>op;
		if(op==1){
			cin>>l>>r;l=get(l),r=get(r);if(l>r)swap(l,r);
			update(l,r);
		}
		if(op==2){
			cin>>l>>r>>val;l=get(l),r=get(r),val=get(val);if(l>r)swap(l,r);
			cout<<(ans=ask(l,r,val))<<'\n';
		}
	}
}

\(\text{Problem}3\text{ CF}226\text{E (}3706\text{)}\)

给定一个大小为 \(n\) 的有根树(根节点不一定为 \(1\)),每个点最初都为白色。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型(设当前操作为第 \(i\) 次操作,操作为 \(1\text{-index}\)):

  1. 给定 \(u\),把 \(u\) 染成黑色。保证该操作对每个点至多执行 \(1\) 次。
  2. 给定 \(u,v,k,t_i\),定义可以选择的点为在操作 \((t_i,i]\) 之间颜色没有变化的点。
    询问在有向链 \(u\rightarrow v\) 上按顺序第 \(k\) 个可选择的点(有向链不包含端点),若不存在这样的点则回答 \(-1\)

保证 \(2\le n,q\le10^5,1\le u,v,k\le n,u\neq v,0\le t_i\le i\)
\(\text{Time Limit }4\text{s},\text{Memory Limit }256\text{MB}\)

$\text{Hint}$

看到路径查询,考虑重链剖分;
看到时间戳,考虑主席树。

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)
可以发现颜色变化的点的数量就是黑点数量之差。


考虑对树进行重链剖分,再套个主席树,维护一段 \(\text{dfn}\) 区间有多少个黑点。

修改就是简单的单点修改。
查询的话稍有点复杂,接下来将详细阐述。

依旧是跳重链。
如果跳的是 \(u\) 所在的重链,判断答案是否在这条链上,若是则直接二分找到,否则将 \(k\) 减去这条重链的贡献。
如果跳的是 \(v\) 所在的重链,则把这条重链压到一个栈里,等到 \(u\) 所在的重链全部处理完再处理。

需要注意的是 \(u\)\(v\) 不算在路径内,需要在代码里特殊处理。
同时存在按 \(\text{dfn}\) 从小到大和从大到小两种顺序进行的二分。
具体可见 \(\text{Solution}\)


时间复杂度 \(O(n)-O(q\log n)-O(q\log^2n)\),空间复杂度 \(O(q\log n)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forG(u,v) for(int __i=head[u],v=to[__i];__i;__i=nxt[__i],v=to[__i])
constexpr int N=1e5+10,M=N<<5;
int n,q,op,u,v,k,t;
int ecnt,head[N],to[N<<1],nxt[N<<1];
void addEdge(int u,int v){++ecnt,to[ecnt]=v,nxt[ecnt]=head[u],head[u]=ecnt;}
int fa[N],sz[N],dep[N],hson[N],dfnc,dfn[N],top[N],rk[N];
void DFS(int rt){
	sz[rt]=1,hson[rt]=-1;
	forG(rt,son){
		dep[son]=dep[rt]+1;
		DFS(son);
		sz[rt]+=sz[son];
		if(hson[rt]==-1||sz[son]>sz[hson[rt]])hson[rt]=son;
	}
}
void DFN(int rt,int tp){
	rk[dfn[rt]=++dfnc]=rt,top[rt]=tp;
	if(hson[rt]==-1)return;
	DFN(hson[rt],tp);
	forG(rt,son)if(son!=hson[rt])DFN(son,son);
}
int tot,root[N],lson[M],rson[M],sum[M];
void update(int pre,int &cur,int l,int r,int pos){
	cur=++tot;
	lson[cur]=lson[pre],rson[cur]=rson[pre],sum[cur]=sum[pre]+1;
	if(l==r)return;
	int mid=l+r>>1;
	if(pos<=mid)update(lson[pre],lson[cur],l,mid,pos);
	else update(rson[pre],rson[cur],mid+1,r,pos);
}
int query(int rt,int l,int r,int L,int R){
	if(!rt)return 0;
	if(L<=l&&r<=R)return sum[rt];
	int mid=l+r>>1,ans=0;
	if(L<=mid)ans+=query(lson[rt],l,mid,L,R);
	if(mid<R)ans+=query(rson[rt],mid+1,r,L,R);
	return ans;
}
int lkth(int rtl,int rtr,int dfnl,int dfnr,int k){
	int l=dfnl,r=dfnr;
	while(l<r){
		int mid=l+r>>1;
		int sum=(mid-dfnl+1)-(query(rtr,1,n,dfnl,mid)-query(rtl,1,n,dfnl,mid));
		if(sum<k)l=mid+1;
		else r=mid;
	}
	return r;
}
int rkth(int rtl,int rtr,int dfnl,int dfnr,int k){
	int l=dfnl,r=dfnr;
	while(l<r){
		int mid=l+r+1>>1;
		int sum=(dfnr-mid+1)-(query(rtr,1,n,mid,dfnr)-query(rtl,1,n,mid,dfnr));
		if(sum>=k)l=mid;
		else r=mid-1;
	}
	return r;
}
void add(int i,int u){update(root[i-1],root[i],1,n,dfn[u]);}
int ask(int i,int u,int v,int k,int t){
	root[i]=root[i-1];
	int U=u,V=v;stack<array<int,2>> stk;
	while(top[u]!=top[v]){
		if(dep[top[u]]>=dep[top[v]]){
			if(u==U)u=fa[u];
			else{
				int sum=(dep[u]-dep[top[u]]+1)-(query(root[i],1,n,dfn[top[u]],dfn[u])-query(root[t],1,n,dfn[top[u]],dfn[u]));
				if(k<=sum)return rk[rkth(root[t],root[i],dfn[top[u]],dfn[u],k)];
				k-=sum,u=fa[top[u]];
			}
		}
		else{
			if(v==V)v=fa[v];
			else{
				stk.push({top[v],v});
				v=fa[top[v]];
			}
		}
	}
	if(dep[u]>dep[v]){
		if(u==U)u=fa[u];
		if(v==V)v=hson[v];
		int sum=(dep[u]-dep[v]+1)-(query(root[i],1,n,dfn[v],dfn[u])-query(root[t],1,n,dfn[v],dfn[u]));
		if(k<=sum)return rk[rkth(root[t],root[i],dfn[v],dfn[u],k)];
		k-=sum;
	}
	else{
		if(u==U)u=hson[u];
		if(v==V)v=fa[v];
		stk.push({u,v});
	}
	while(!stk.empty()){
		auto [u,v]=stk.top();stk.pop();
		int sum=(dep[v]-dep[u]+1)-(query(root[i],1,n,dfn[u],dfn[v])-query(root[t],1,n,dfn[u],dfn[v]));
		if(k<=sum)return rk[lkth(root[t],root[i],dfn[u],dfn[v],k)];
		k-=sum;
	}
	return -1;
}
void solve(){
	cin>>n;
	forUp(i,1,n)cin>>fa[i],addEdge(fa[i],i);
	DFS(0);DFN(0,0);++n;
	cin>>q;
	forUp(i,1,q){
		cin>>op;
		if(op==1)cin>>u,add(i,u);
		if(op==2)cin>>u>>v>>k>>t,cout<<ask(i,u,v,k,t)<<'\n';
	}
}

\(\text{Problem}4\text{ CF}633\text{G (}2778\text{)}\)

给定一个正整数 \(V\) 和一棵大小为 \(n\) 的有根树,每个点有初始点权 \(A_i\)
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(u\) 和自然数 \(val\),对于 \(\forall v\in\operatorname{subtree}(u)\),执行 \(A_v\leftarrow A_v+val\)
  2. 给定 \(u\),求有多少个满足条件的质数 \(P\)\(P<V,\exists v\in\operatorname{subtree}(u),k\in\mathbb N,A_v=P+V\cdot k\)

保证 \(1\le n,q\le10^5,1\le V\textcolor{red}{\le1000},1\le u\le n,0\le A_i,val\le10^9\)
\(\text{Time Limit }\textcolor{red}{4\text{s}},\text{Memory Limit }256\text{MB}\)

$\text{Hint}$

条件显然可以转化为 \(P<V,\exists v\in\operatorname{subtree}(u),A_v\equiv P\pmod V\)
可以发现,我们只关心一个区间内一个数在模 \(V\) 的意义下是否出现过。

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)


考虑用 \(\text{bitset}\) 维护区间是否有一个数在模 \(V\) 的意义下是否出现过。

可以发现,修改相当于对 \(\text{bitset}\) 循环移位,查询相当于求区间的 \(\text{bitset}\)
在线段树上打 \(\text{lazytag}\) 可以轻松解决。

时间复杂度 \(O(\dfrac{qV\log n}{w})\),空间复杂度 \(O(\dfrac{nV}{w})\)

由于值域较小且时限较松所以可以过。

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forG(u,v) for(int __i=head[u],v=to[__i];__i;__i=nxt[__i],v=to[__i])
constexpr int N=1e5+10,M=1000;
int n,V,A[N],u,v,q,op,val;
int ecnt,head[N],to[N<<1],nxt[N<<1];
void addEdge(int u,int v){
	++ecnt,to[ecnt]=v,nxt[ecnt]=head[u],head[u]=ecnt;
	++ecnt,to[ecnt]=u,nxt[ecnt]=head[v],head[v]=ecnt;
}
int DFN,bg[N],ed[N],rk[N];
void dfs(int rt,int fa){
	rk[bg[rt]=++DFN]=rt;
	forG(rt,son)if(son!=fa)dfs(son,rt);
	ed[rt]=DFN;
}
int pcnt,P[M];bitset<M> isp;
void sieve(int V){
	forUP(i,2,V)isp[i]=1;
	forUP(i,2,V){
		if(isp[i])P[++pcnt]=i;
		for(int j=1;j<=pcnt&&i*P[j]<V;++j){
			isp[i*P[j]]=0;
			if(i%P[j]==0)break;
		}
	}
}
bitset<M> bucket[N<<2];int add[N<<2];
void pushup(int rt){bucket[rt]=bucket[rt<<1]|bucket[rt<<1|1];}
void modify(int rt,int tag){
	(add[rt]+=tag)%=V;
	bucket[rt]=(bucket[rt]<<tag)|(bucket[rt]>>V-tag);
}
void pushdown(int rt){
	if(add[rt]){
		modify(rt<<1,add[rt]);modify(rt<<1|1,add[rt]);
		add[rt]=0;
	}
}
void build(int rt,int l,int r){
	if(l==r){
		bucket[rt][A[rk[l]]%V]=1;
		return;
	}
	int mid=l+r>>1;
	build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
	pushup(rt);
}
void update(int rt,int l,int r,int L,int R,int val){
	if(L<=l&&r<=R)return modify(rt,val);
	int mid=l+r>>1;
	pushdown(rt);
	if(L<=mid)update(rt<<1,l,mid,L,R,val);
	if(mid<R)update(rt<<1|1,mid+1,r,L,R,val);
	pushup(rt);
}
bitset<M> query(int rt,int l,int r,int L,int R){
	if(L<=l&&r<=R)return bucket[rt];
	int mid=l+r>>1;
	pushdown(rt);
	if(R<=mid)return query(rt<<1,l,mid,L,R);
	else if(mid<L)return query(rt<<1|1,mid+1,r,L,R);
	else return query(rt<<1,l,mid,L,R)|query(rt<<1|1,mid+1,r,L,R);
}
void solve(){
	cin>>n>>V;
	forUp(i,1,n)cin>>A[i];
	forUP(i,1,n)cin>>u>>v,addEdge(u,v);
	dfs(1,0);
	build(1,1,n);
	sieve(V);
	cin>>q;
	while(q--){
		cin>>op;
		if(op==1){
			cin>>u>>val;
			update(1,1,n,bg[u],ed[u],val%V);
		}
		if(op==2){
			cin>>u;
			cout<<(query(1,1,n,bg[u],ed[u])&isp).count()<<'\n';
		}
	}
}

\(\text{Problem}5\text{ CF}1422\text{F (}2943\text{)}\)

给定一个长度为 \(n\) 的正整数序列 \(A\)
接下来有 \(q\) 次询问,每次给定 \(l,r\),询问 \(\operatorname{lcm}(A_{l\sim r})\bmod (10^9+7)\)

强制在线。
保证 \(1\le n,q\le10^5,1\le A_i\le2\cdot10^5,1\le l\le r\le n\)
\(\text{Time Limit }3\text{s},\text{Memory Limit }512\text{MB}\)

$\text{Hints}$
$\text{Hint}1$

考虑分解质因数,对每个质数的贡献分开考虑。

$\text{Hint}2$

请尝试解决可以离线的原问题,再考虑怎么变成在线。

$\text{Hint}3$

考虑在序列末尾加一个数对答案的贡献。

$\text{Tutorial}$

请先阅读 \(\text{Hints}\)


考虑离线做法。

考虑对 \(r\) 扫描线,维护每个 \(l\) 的答案。

对于每个质数,我们考虑从右往左的增量,并在对应的位置设为增量,则询问就变成求区间积。
我们用单调栈维护每个质数增量出现的位置和次数(注意记录的不是增量),往右扫的时候维护单调栈和线段树即可。

现在要求强制在线,把线段树换成可持久化线段树即可。

时间复杂度 \(O(n\log n\log V)-O(q\log n)\),空间复杂度 \(O(n\log n\log V)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=1e5+10,V=2e5+10,M=300*1e5+10,mod=1e9+7;
int n,A[N],q,l,r,ans;
int get(int x){return (ans+x)%n+1;}
int tot,root[N],lson[M],rson[M],mul[M];
void build(int &rt,int l,int r){
	mul[rt=++tot]=1;
	if(l==r)return;
	int mid=l+r>>1;
	build(lson[rt],l,mid);build(rson[rt],mid+1,r);
}
void update(int pre,int &cur,int l,int r,int pos,int val){
	cur=++tot;
	lson[cur]=lson[pre],rson[cur]=rson[pre],mul[cur]=1ll*mul[pre]*val%mod;
	if(l==r)return;
	int mid=l+r>>1;
	if(pos<=mid)update(lson[pre],lson[cur],l,mid,pos,val);
	else update(rson[pre],rson[cur],mid+1,r,pos,val);
}
int query(int rt,int l,int r,int L,int R){
	if(!rt||R<l||r<L)return 1;
	if(L<=l&&r<=R)return mul[rt];
	int mid=l+r>>1,ans=1;
	if(L<=mid)ans=1ll*ans*query(lson[rt],l,mid,L,R)%mod;
	if(mid<R)ans=1ll*ans*query(rson[rt],mid+1,r,L,R)%mod;
	return ans;
}
int inv[V],minp[V];
void sieve(int n=V-10){
	minp[1]=1;
	forUp(i,2,n)if(!minp[i])for(int j=i;j<=n;j+=i)minp[j]=i;
	inv[0]=inv[1]=1;
	forUp(i,2,n)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
}
stack<array<int,2>> stk[V];
void solve(){
	cin>>n;
	forUp(i,1,n)cin>>A[i];
	sieve();
	build(root[0],1,n);
	forUp(i,1,n){
		root[i]=root[i-1];
		while(A[i]>1){
			int p=minp[A[i]],k=1;
			while(A[i]%p==0)A[i]/=p,k*=p;int lst=1;
			while(!stk[p].empty()&&stk[p].top()[1]<k){
				auto [pos,val]=stk[p].top();stk[p].pop();
				update(root[i],root[i],1,n,pos,1ll*inv[val]*lst%mod);
				lst=val;
			}
			if(!stk[p].empty())update(root[i],root[i],1,n,stk[p].top()[0],1ll*inv[k]*lst%mod);
			update(root[i],root[i],1,n,i,k);
			stk[p].push({i,k});
		}
	}
	cin>>q;
	while(q--){
		cin>>l>>r;l=get(l),r=get(r);if(l>r)swap(l,r);
		cout<<(ans=query(root[r],1,n,l,r))<<'\n';
	}
}

\(\text{Problem}6\text{ CF}1749\text{F (}2918\text{)}\)

给定一棵大小为 \(n\) 的有根树,最初每个点的点权为 \(0\)
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(u\),询问 \(u\) 的点权。
  2. 给定 \(u,v,val,d\),将满足到链 \(u\rightarrow v\) 的距离 \(\le d\) 所有点的点权加上 \(val\)
    点到链的距离定义为该点到该链上任意一点的距离的最小值。

保证 \(2\le n\le2\cdot10^5,1\le q\le2\cdot10^5,1\le u,v\le n,1\le val\le1000,0\le d\textcolor{red}{\le20}\)
\(\text{Time Limit }4\text{s},\text{Memory Limit }512\text{MB}\)

$\text{Hint}$

发现 \(d\) 很小,直接跳祖先复杂度可以接受,考虑把贡献挂到点的祖先上。

$\text{Tutorial}$

我们令 \(f_{k,u}\)\(u\) 子树内到 \(u\) 的距离为 \(k\) 的点的加法标记。
则查询就是对 \(u\)\(k\) 级祖先 \(v\)\(\sum f_{k,v}\)

现在考虑如何修改,令 \(lca=\operatorname{LCA}(u,v)\)
稍微讨论下可以发现,修改可以转化为 \(3\) 次对到根链的 \(f_d\) 加,以及对 \(lca\) 的祖先共做 \(O(d)\) 次单点加。
到根链加可以变成单点加子树和,单点加对询问的贡献可以另外计算。

时间复杂度 \(O(q(\log n+d))-O(qd\log n)\),空间复杂度 \(O(nd)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forG(u,v) for(int __i=head[u],v=to[__i];__i;__i=nxt[__i],v=to[__i])
constexpr int N=2e5+10,D=21;
int n,u,v,q,op,d,val;
int ecnt,head[N],to[N<<1],nxt[N<<1];
void addEdge(int u,int v){
	++ecnt,to[ecnt]=v,nxt[ecnt]=head[u],head[u]=ecnt;
	++ecnt,to[ecnt]=u,nxt[ecnt]=head[v],head[v]=ecnt;
}
int DFN,fa[20][N],dep[N],bg[N],ed[N];
void dfs(int rt){
	bg[rt]=++DFN,dep[rt]=dep[fa[0][rt]]+1;
	for(int i=0;fa[i][rt];++i)fa[i+1][rt]=fa[i][fa[i][rt]];
	forG(rt,son)if(son!=fa[0][rt])fa[0][son]=rt,dfs(son);
	ed[rt]=DFN;
}
int LCA(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	forDown(i,19,0)if(dep[fa[i][u]]>=dep[v])u=fa[i][u];
	if(u==v)return u;
	forDown(i,19,0)if(fa[i][u]!=fa[i][v])u=fa[i][u],v=fa[i][v];
	return fa[0][u];
}
struct fenwick{
	int c[N];
	void add(int pos,int val){for(;pos<=n;pos+=pos&-pos)c[pos]+=val;}
	int ask(int pos){int ans=0;for(;pos;pos-=pos&-pos)ans+=c[pos];return ans;}
	int ask(int l,int r){return ask(r)-ask(l-1);}
}f[D];int g[D][N];
void solve(){
	cin>>n;
	forUP(i,1,n)cin>>u>>v,addEdge(u,v);
	dfs(1);
	cin>>q;
	while(q--){
		cin>>op;
		if(op==1){
			cin>>u;int ans=0;
			for(int k=0;k<D&&u;u=fa[0][u],++k)ans+=f[k].ask(bg[u],ed[u])+g[k][u];
			cout<<ans<<'\n';
		}
		if(op==2){
			cin>>u>>v>>val>>d;int lca=LCA(u,v);
			f[d].add(bg[u],val);f[d].add(bg[v],val);f[d].add(bg[lca],-2*val);
			for(int k=0;k<=d;++k,lca=fa[0][lca]){
				g[d-k][lca]+=val;
				if(k<d)g[d-k-1][lca]+=val;
				if(lca==1){
					for(k+=2;k<=d;++k)g[d-k][lca]+=val;
					break;
				}
			}
		}
	}
}

\(\text{Problem}7\text{ CF}1010\text{E (}2803\text{)}\)

给定 \(3\) 个正整数 \(X,Y,Z\),接下来提到的所有点和长方体都在长方体 \(([1,X],[1,Y],[1,Z])\) 内。
有一个隐藏的长方体 \(C\),给定 \(n_1\) 个在 \(C\) 内的点和 \(n_2\) 个不在 \(C\) 内的点。

注意给出的信息可能有矛盾,该情况下不用继续回答询问。

接下来有 \(q\) 次询问,每次询问给定一个点,询问该点是否:

  1. 一定在 \(C\) 内。
  2. 一定不在 \(C\) 内。
  3. 都有可能。

保证 \(1\le X,Y,Z,n_1,q\le10^5,0\le n_2\le10^5\)
\(\text{Time Limit }2\text{s},\text{Memory Limit }256\text{MB}\)

$\text{Hint}$ $1$ 是易于判断的,重点在于 $2,3$。

考虑一个点如果在 \(C\) 内会怎么样。

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)


如果一个点在 \(C\) 内会得出矛盾,则它一定不在 \(C\) 内,否则不确定。

因此问题就转化为三维数点,\(\text{K-D Tree}\) 即可。

时间复杂度 \(O(n)-O(qn^{\frac{2}{3}})\),空间复杂度 \(O(n)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forUP(i,a,b) for(int i=(a);i<(b);++i)
constexpr int N=1e5+10,K=3;
int n1,n2,q;array<int,K> lim,pos,L,R;
struct KDTnode{
	array<int,K> pos,L,R;
	int lson,rson;
}KDT[N];int root;
void pushup(int rt){
	forUP(k,0,K){
		KDT[rt].L[k]=KDT[rt].R[k]=KDT[rt].pos[k];
		if(KDT[rt].lson)chkMin(KDT[rt].L[k],KDT[KDT[rt].lson].L[k]),chkMax(KDT[rt].R[k],KDT[KDT[rt].lson].R[k]);
		if(KDT[rt].rson)chkMin(KDT[rt].L[k],KDT[KDT[rt].rson].L[k]),chkMax(KDT[rt].R[k],KDT[KDT[rt].rson].R[k]);
	}
}
int build(int l,int r,int dep=0){
	if(l>r)return 0;
	int mid=l+r>>1,rt=mid;nth_element(KDT+l,KDT+mid,KDT+r,[dep](const KDTnode &lhs,const KDTnode &rhs)->bool{return lhs.pos[dep]<rhs.pos[dep];});
	if(l<mid)KDT[rt].lson=build(l,mid-1,(dep+1)%K);
	if(mid<r)KDT[rt].rson=build(mid+1,r,(dep+1)%K);
	pushup(rt);
	return rt;
}
void query(int rt,array<int,K> L,array<int,K> R,bool &ans){
	if(ans)return;
	if(!rt)return;
	bool in=true;forUP(k,0,K)in&=L[k]<=KDT[rt].pos[k]&&KDT[rt].pos[k]<=R[k];
	if(in){ans=true;return;}
	forUP(k,0,K)if(KDT[rt].R[k]<L[k]||R[k]<KDT[rt].L[k])return;
	query(KDT[rt].lson,L,R,ans);query(KDT[rt].rson,L,R,ans);
}
void solve(){
	cin>>lim[0]>>lim[1]>>lim[2]>>n1>>n2>>q;
	L=lim,R={1,1,1};
	forUp(i,1,n1){
		forUP(k,0,K)cin>>pos[k];
		forUP(k,0,K)chkMin(L[k],pos[k]),chkMax(R[k],pos[k]);
	}
	forUp(i,1,n2){
		forUP(k,0,K)cin>>KDT[i].pos[k];
		bool in=true;
		forUP(k,0,K)in&=L[k]<=KDT[i].pos[k]&&KDT[i].pos[k]<=R[k];
		if(in){
			cout<<"INCORRECT\n";
			return;
		}
	}
	cout<<"CORRECT\n";root=build(1,n2);
	while(q--){
		forUP(k,0,K)cin>>pos[k];
		bool in=true;
		forUP(k,0,K)in&=L[k]<=pos[k]&&pos[k]<=R[k];
		if(in){
			cout<<"OPEN\n";
			continue;
		}
		array<int,K> tL,tR;forUP(k,0,K)tL[k]=min(L[k],pos[k]),tR[k]=max(R[k],pos[k]);
		query(root,tL,tR,in);
		if(in)cout<<"CLOSED\n";
		else cout<<"UNKNOWN\n";
	}
}

\(\text{Problem}8\text{ CF}576\text{E (}3461\text{)}\)

给定一个包含 \(n\) 个点 \(m\) 条边的无向图 \(G\)\(k\) 种颜色,每条边可以不染或染成颜色 \(1\sim k\)
最初所有边都未染色。

接下来有 \(q\) 次操作,每次操作给定一条在 \(G\) 上的边 \(edge\) 和颜色 \(c\)

  • 若把 \(edge\) 染成颜色 \(c\) 后,对于相同颜色所形成的子图仍为二分图,则保留该操作,并回答操作成功。
  • 否则,该操作不生效,回答操作失败。

保证 \(2\le n\le5\cdot10^5,1\le m,q\le5\cdot10^5,1\le k\textcolor{red}{\le50},1\le c\le k\)
\(\text{Time Limit }6\text{s},\text{Memory Limit }\textcolor{red}{600\text{MB}}\)

$\text{Hint}$

发现和加边删边判断二分图类似,考虑线段树分治。

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)


对于一条边,前后两次染色的时间为 \(x,y\),则染色的时间区间为 \((x,y)\)
颜色有两种可能,一种是染上去了,一种是维持原样。

我们只需要分治到叶子后判断是否满足条件,据此改变边的颜色即可。

由于 \(k\) 比较小,可以直接开 \(k\) 个可撤销并查集维护。

时间复杂度 \(O(m\log n\log q)\),空间复杂度 \(O(nk+m\log q)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=5e5+10,V=50+1;
int n,m,k,q,u[N],v[N],eid[N],c[N];
int fa[V][N<<1],sz[V][N<<1],tp,stkU[N<<1],stkV[N<<1],stkC[N<<1];
void init(int k,int n){forUp(i,1,k)forUp(j,1,n<<1)sz[i][fa[i][j]=j]=1;}
int find(int id,int x){return x==fa[id][x]?x:find(id,fa[id][x]);}
void merge(int id,int x,int y){
	x=find(id,x),y=find(id,y);
	if(x==y)return;
	if(sz[id][x]<sz[id][y])swap(x,y);
	fa[id][y]=x,sz[id][x]+=sz[id][y];
	stkU[++tp]=x,stkV[tp]=y,stkC[tp]=id;
}
void undo(){
	int x=stkU[tp],y=stkV[tp],id=stkC[tp--];
	fa[id][y]=y,sz[id][x]-=sz[id][y];
}
int col[N],lst[N];vector<int> edges[N<<2];
void insert(int rt,int l,int r,int L,int R,int id){
	if(L<=l&&r<=R)return edges[rt].pushb(id);
	int mid=l+r>>1;
	if(L<=mid)insert(rt<<1,l,mid,L,R,id);
	if(mid<R)insert(rt<<1|1,mid+1,r,L,R,id);
}
void dfs(int rt,int l,int r){
	int lsttp=tp;
	for(int id:edges[rt]){
		int x=u[eid[id]],y=v[eid[id]],w=c[id];
		merge(w,find(w,x),find(w,y+n));
		merge(w,find(w,x+n),find(w,y));
	}
	if(l==r){
		int w=c[l],x=u[eid[l]],y=v[eid[l]];
		if(find(w,x)==find(w,y))cout<<"NO\n",c[l]=col[eid[l]];
		else cout<<"YES\n",col[eid[l]]=c[l];
	}
	else{
		int mid=l+r>>1;
		dfs(rt<<1,l,mid);dfs(rt<<1|1,mid+1,r);
	}
	while(lsttp!=tp)undo();
}
void solve(){
	cin>>n>>m>>k>>q;init(k,n);
	forUp(i,1,m)cin>>u[i]>>v[i];
	forUp(i,1,q)cin>>eid[i]>>c[i],lst[eid[i]]=q+1;
	forDown(i,q,1){
		if(i+1<lst[eid[i]])insert(1,1,q,i+1,lst[eid[i]]-1,i);
		lst[eid[i]]=i;
	}
	dfs(1,1,q);
}

\(\text{Problem}9\text{ CF}1491\text{H (}3500\text{)}\)

给定一棵大小为 \(n\) 的有根树的父亲序列 \(fa\)
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(l,r,val\),对于 \(\forall l\le i\le r\),执行 \(fa_i\leftarrow \max(1,fa_i-val)\)
  2. 给定 \(u,v\),询问 \(\operatorname{LCA}(u,v)\)

保证 \(2\le n,q\le10^5,1\le fa_i\textcolor{red}{<i},2\le l\le r\le n,\textcolor{red}{1\le}val\le10^5,1\le u,v\le n\)
\(\text{Time Limit }1.5\text{s},\text{Memory Limit }256\text{MB}\)

$\text{Hint}$

仿照弹飞绵羊的分块做法,考虑维护一个点第一个与其不在同一块的祖先 \(pre\)

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)
设分块的块长为 \(S\)


考虑如何做修改。

可以发现,一个整块最多减 \(O(S)\)\(fa_i\) 就和 \(i\) 不在同一块,此时整块打加法标记即可。
散块可以暴力减后暴力重构,整块在可以打标记之前也可以直接暴力重构。
修改的总体时间复杂度是 \(O(nS+q(S+\dfrac{n}{S}))\)

考虑如何查询 \(\operatorname{LCA}(u,v)\)
考虑和树链剖分求 \(\operatorname{LCA}\) 类似的方式:

  • \(u\)\(v\) 不在同一块,则所在块编号大的点跳到 \(pre\)
  • 否则若 \(pre_u\neq pre_v\),则 \(u\)\(v\) 同时跳到 \(pre\)
  • 否则编号大的点跳到 \(fa\),直到相等为止。

可以发现跳 \(pre\) 的操作至多有 \(O(\dfrac{n}{S})\) 次,跳 \(fa\) 的操作至多有 \(O(S)\) 次。
所以单次查询的时间复杂度为 \(O(S+\dfrac{n}{S})\)

\(S=\sqrt{n}\) 时取到最优复杂度。

时间复杂度 \(O(n)-O((n+q)\sqrt{n})-O(q\sqrt{n})\),空间复杂度 \(O(n)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=1e5+10,TOT=318+10;
int n,q,op,l,r,val,pos1,pos2;
int B,tot,blng[N],L[TOT],R[TOT],fa[N],pre[N],cnt[TOT],add[TOT];
void refresh(int bid){
	forUp(pos,L[bid],R[bid])fa[pos]=max(1,fa[pos]+add[bid]);add[bid]=0;
	forUp(pos,L[bid],R[bid]){
		if(fa[pos]<L[bid])pre[pos]=fa[pos];
		else pre[pos]=pre[fa[pos]];
	}
}
void build(){
	B=sqrt(n),tot=(n-1)/B+1;
	L[0]=1-B;forUp(i,1,tot){
		L[i]=L[i-1]+B,R[i]=min(R[i-1]+B,n);
		forUp(pos,L[i],R[i])blng[pos]=i;
	}
	forUp(i,1,tot)refresh(i);
}
void update(int l,int r,int val){
	int lb=blng[l],rb=blng[r];
	if(lb==rb){
		forUp(pos,l,r)fa[pos]=max(1,fa[pos]+val);
		refresh(lb);
	}
	else{
		forUp(pos,l,R[lb])fa[pos]=max(1,fa[pos]+val);refresh(lb);
		forUp(pos,L[rb],r)fa[pos]=max(1,fa[pos]+val);refresh(rb);
		forUp(bid,lb+1,rb-1){
			++cnt[bid],add[bid]=max(-n,add[bid]+val);
			if(cnt[bid]<=B)refresh(bid);
		}
	}
}
int getpre(int pos){return max(1,pre[pos]+add[blng[pos]]);}
int LCA(int pos1,int pos2){
	while(true){
		if(blng[pos1]<blng[pos2])swap(pos1,pos2);
		if(blng[pos1]!=blng[pos2])pos1=getpre(pos1);
		else if(getpre(pos1)!=getpre(pos2))pos1=getpre(pos1),pos2=getpre(pos2);
		else break;
	}
	while(pos1!=pos2){
		if(pos1<pos2)swap(pos1,pos2);
		pos1=max(1,fa[pos1]+add[blng[pos1]]);
	}
	return pos1;
}
void solve(){
	cin>>n>>q;
	forUp(i,2,n)cin>>fa[i];
	build();
	while(q--){
		cin>>op;
		if(op==1){
			cin>>l>>r>>val;
			update(l,r,-val);
		}
		if(op==2){
			cin>>pos1>>pos2;
			cout<<LCA(pos1,pos2)<<'\n';
		}
	}
}

\(\text{Problem}10\text{ CF}575\text{I (}3140\text{)}\)

给定一个正整数 \(n\),接下来提到的点和三角形都在矩形 \(([1,n],[1,n])\) 内。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(dir,x,y,len\),要求在腰长为 \(len\),直角的坐标为 \((x,y)\) 的等腰直角三角形内(包含边界)的点的权值加 \(1\)
    \(dir=1\) 时,直角在左下角;当 \(dir=2\) 时,直角在左上角;
    \(dir=3\) 时,直角在右下角;当 \(dir=4\) 时,直角在右上角。
  2. 给定 \(x,y\),询问点 \((x,y)\) 的权值。

保证 \(1\le n\textcolor{red}{\le5000},1\le q\le10^5,1\le dir\le 4\)
\(\text{Time Limit }1.5\text{s},\text{Memory Limit }512\text{MB}\)

$\text{Hints}$
$\text{Hint}1$

注意到 \(4\) 种情况是对称的,考虑如何处理 \(dir=1\) 的情况。

$\text{Hint}2$

发现修改操作本质的限制为 \(x'+y'\in[x+y,x+y+len],x'\in[x,n],y'\in[y,n]\)
考虑拆成几个部分,使得限制更为简单。

$\text{Tutorial}$

请先阅读 \(\text{Hints}\)




注意到题目所求,即蓝色部分,等于红色部分减黄色部分。

注意到红色部分满足 \(x'+y'\in[x+y,x+y+len],y'\in[y,n]\),黄色部分满足 \(x'+y'\in[x+y,x+y+len],x'\in[1,x)\)
考虑开两棵二维树状数组,分别维护 \((x+y,x)\)\((x+y,y)\),则变成 \(4\text{-side}\) 矩形加单点查。
差分后就变成单点加 \(2\text{-side}\) 矩形查。

时间复杂度 \(O(q\log^2n)\),空间复杂度 \(O(n^2)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int Q=1e5+10;
int n,q,op,dir[Q],x[Q],y[Q],len[Q],ans[Q];
struct fenwick{
	int n,m;
	vector<vector<int>> BIT;
	void resize(int _n,int _m){
		n=_n,m=_m;
		BIT.resize(n+1);
		forUp(i,0,n)BIT[i].resize(m+1);
		forUp(i,0,n)forUp(j,0,m)BIT[i][j]=0;
	}
	void add(int X,int Y,int val){for(int x=X;x<=n;x+=x&-x)for(int y=Y;y<=m;y+=y&-y)BIT[x][y]+=val;}
	void add(int U,int D,int L,int R,int val){
		add(D+1,R+1,val);
		add(D+1,L,-val);
		add(U,R+1,-val);
		add(U,L,val);
	}
	int ask(int X,int Y){int ans=0;for(int x=X;x;x-=x&-x)for(int y=Y;y;y-=y&-y)ans+=BIT[x][y];return ans;}
}Tx,Ty;
void resize(int n,int m){Tx.resize(n,m);Ty.resize(n,m);}
void add(int x,int y,int len,int val){
	Tx.add(x+y,x+y+len,1,x-1,val);
	Ty.add(x+y,x+y+len,y,n,val);
}
int ask(int x,int y){return Ty.ask(x+y,y)-Tx.ask(x+y,x);}
void solve(){
	cin>>n>>q;
	forUp(i,1,q){
		cin>>op;
		if(op==1)cin>>dir[i]>>x[i]>>y[i]>>len[i];
		if(op==2)dir[i]=5,cin>>x[i]>>y[i];
	}
	forUp(op,1,4){
		resize(n<<1,n);
		forUp(i,1,q)if(dir[i]==op||dir[i]==5){
			int tx,ty;
			if(op==1)tx=x[i],ty=y[i];
			if(op==2)tx=x[i],ty=n-y[i]+1;
			if(op==3)tx=n-x[i]+1,ty=y[i];
			if(op==4)tx=n-x[i]+1,ty=n-y[i]+1;
			if(dir[i]==op)add(tx,ty,len[i],1);
			if(dir[i]==5)ans[i]+=ask(tx,ty);
		}
	}
	forUp(i,1,q)if(dir[i]==5)cout<<ans[i]<<'\n';
}

\(\text{Problem}11\text{ CF}1667\text{E (}2964\text{)}\)

给定一个长度为 \(n\) 的排列 \(A\)
定义区间 \([l,r]\) 为合法的,当且仅当 \(\exists (i,j),l\le i<j\le r,A_i\cdot A_j=\max\{A_l,A_{l+1},\cdots,A_r\}\)
接下来有 \(q\) 次询问,每次给定 \(l,r\),询问区间 \([l,r]\) 有多少个合法的子区间 \([x,y]\)

保证 \(1\le n\le2\cdot10^5,1\le q\le10^6,1\le l\le r\le n\)
\(\text{Time Limit }4\text{s},\text{Memory Limit }1024\text{MB}\)

$\text{Hints}$
$\text{Hint}1$

套路考虑单调栈预处理 \(A_i\) 能成为最大值的极大区间 \([L_i,R_i]\)

$\text{Hint}2$

考虑预处理固定 \(r\) 时合法的 \(l\) 的区间 \([L,R]\),或者固定 \(l\) 时合法的 \(r\) 的区间 \([L,R]\)

$\text{Tutorial}$

请先阅读 \(\text{Hints}\)


考虑对于 \(\text{Hint}1\) 里提到的每个区间 \([L_i,R_i]\) 预处理 \(\text{Hint}2\) 中提到的信息。
然后就可以扫描线来解决这个问题。

考虑启发式分裂,每次遍历左右两个中更小的区间,可以使得到的信息条数为 \(O(n\log n)\) 的。


详细讲讲如何预处理 \(\text{Hint}2\) 中提到的信息,以固定 \(r\) 为例。

\(pos_v\)\(v\) 在序列中的位置,且有初始值 \(L=L_i,R=L_i-1\)

首先枚举 \(A_i\) 的约数 \(x\),令 \(y=\dfrac{A_i}{x}\)
对于所有 \(L_i\le pos_x,pos_y\le i\),执行 \(R\leftarrow\max(R,\min(pos_x,pos_y))\)

然后向右移动 \(r\),若满足 \(A_i\bmod A_r=0\)\(A_r^2\neq A_i\),令 \(x=A_r,y=\dfrac{A_i}{A_r}\)
若还满足 \(L_i\le pos_y\le r\),则执行 \(R\leftarrow\max(R,\min(pos_y,i))\)

这样就可以求出对于固定的 \(r\) 合法的 \(l\) 的区间 \([L,R]\)

预处理每个数的约数,时间和空间复杂度为 \(O(n\ln n)\)


时间复杂度 \(O(n\log^2n+q\log n)\),空间复杂度 \(O(n\log n+q)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
template<class _Tp>void chkMax(_Tp &x,const _Tp &y){x<y?x=y:0;}
template<class _Tp>void chkMin(_Tp &x,const _Tp &y){x>y?x=y:0;}
constexpr int N=2e5+10,Q=1e6+10;
int n,q,A[N],l,r;int64 ans[Q];
int pos[N],tp,stk[N],L[N],R[N];vector<array<int,3>> vecL[N],vecR[N];vector<int> fac[N];
int64 sum[N<<2],add[N<<2];
void pushup(int rt){sum[rt]=sum[rt<<1]+sum[rt<<1|1];}
void modify(int rt,int l,int r,int64 tag){
	add[rt]+=tag;
	sum[rt]+=(r-l+1)*tag;
}
void pushdown(int rt,int l,int r){
	if(add[rt]){
		int mid=l+r>>1;
		modify(rt<<1,l,mid,add[rt]);modify(rt<<1|1,mid+1,r,add[rt]);
		add[rt]=0;
	}
}
void build(int rt,int l,int r){
	sum[rt]=add[rt]=0;
	if(l==r)return;
	int mid=l+r>>1;
	build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
}
void update(int rt,int l,int r,int L,int R,int val){
	if(L<=l&&r<=R)return modify(rt,l,r,val);
	int mid=l+r>>1;
	pushdown(rt,l,r);
	if(L<=mid)update(rt<<1,l,mid,L,R,val);
	if(mid<R)update(rt<<1|1,mid+1,r,L,R,val);
	pushup(rt);
}
int64 query(int rt,int l,int r,int L,int R){
	if(L<=l&&r<=R)return sum[rt];
	int mid=l+r>>1;int64 ans=0;
	pushdown(rt,l,r);
	if(L<=mid)ans+=query(rt<<1,l,mid,L,R);
	if(mid<R)ans+=query(rt<<1|1,mid+1,r,L,R);
	return ans;
}
void solve(){
	cin>>n>>q;
	forUp(i,1,n)cin>>A[i];
	forUp(i,1,n)pos[A[i]]=i;
	forUp(i,1,n)L[i]=1,R[i]=n;
	forUp(i,1,n){
		while(tp&&A[stk[tp]]<A[i])R[stk[tp--]]=i-1;
		L[i]=stk[tp]+1,stk[++tp]=i;
	}
	forUp(i,1,n)for(int j=i;j<=n;j+=i)fac[j].pushb(i);
	forUp(i,1,n){
		if(R[i]-i<i-L[i]){
			l=L[i],r=L[i]-1;
			for(auto x:fac[A[i]]){
				int y=A[i]/x;if(x>=y)break;
				if(L[i]<=pos[x]&&pos[x]<=i&&L[i]<=pos[y]&&pos[y]<=i)chkMax(r,min(pos[x],pos[y]));
			}
			forUp(j,i,R[i]){
				if(A[i]%A[j]==0){
					int x=A[j],y=A[i]/A[j];
					if(x!=y&&L[i]<=pos[y]&&pos[y]<=j)chkMax(r,pos[y]),chkMin(r,i);
				}
				if(l<=r)vecR[j].pushb({0,l,r});
			}
		}
		else{
			l=R[i]+1,r=R[i];
			for(auto x:fac[A[i]]){
				int y=A[i]/x;if(x>=y)continue;
				if(i<=pos[x]&&pos[x]<=R[i]&&i<=pos[y]&&pos[y]<=R[i])chkMin(l,max(pos[x],pos[y]));
			}
			forDown(j,i,L[i]){
				if(A[i]%A[j]==0){
					int x=A[j],y=A[i]/A[j];
					if(x!=y&&j<=pos[y]&&pos[y]<=R[i])chkMin(l,pos[y]),chkMax(l,i);
				}
				if(l<=r)vecL[j].pushb({0,l,r});
			}
		}
	}
	forUp(i,1,q){
		cin>>l>>r;
		vecL[l].pushb({i,l,r});
		vecR[r].pushb({i,l,r});
	}
	build(1,1,n);
	forDown(i,n,1)for(auto [qid,l,r]:vecL[i]){
		if(qid==0)update(1,1,n,l,r,1);
		else ans[qid]+=query(1,1,n,l,r);
	}
	build(1,1,n);
	forUp(i,1,n)for(auto [qid,l,r]:vecR[i]){
		if(qid==0)update(1,1,n,l,r,1);
		else ans[qid]+=query(1,1,n,l,r);
	}
	forUp(i,1,q)cout<<ans[i]<<'\n';
}

\(\text{Problem}12\text{ CF}1635\text{F (}2958\text{)}\)

给定一个长度为 \(n\) 的严格递增整数序列 \(x\) 和正整数序列 \(w\)
定义 \(\operatorname{dist}(i,j)=|x_i-x_j|\cdot(w_i+w_j)\)

接下来有 \(q\) 次询问,每次询问给定 \(l,r\),询问 \(\min\limits_{l\le l'<r'\le r}\{\operatorname{dist}(l',r')\}\)

保证 \(2\le n\le3\cdot10^5,1\le q\le3\cdot10^5,-10^9\le x_i\le10^9,1\le w_i\le10^9,1\le l<r\le n\)
\(\text{Time Limit }3\text{s},\text{Memory Limit }256\text{MB}\)

$\text{Hint}$

考虑可能的答案 \((l',r')\) 的形式。

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)


\(L_i=\max\{pos|pos<i,w_{pos}\le w_i\},R_i=\min\{pos|pos>i,w_{pos}\le w_i\}\)

有结论:答案一定满足 \((L_i,i)\)\((i,R_i)\) 的形式。

$\text{Proof}$

设当前的答案 \((i,j)\) 不满足其中任何一种形式。

不妨设 \(w_i\le w_j\)\(w_i>w_j\) 的情况也可类似讨论。

可以发现 \(i<L_j\),并且 \((i,L_j)\) 优于 \((i,j)\)

可以不断调整直至满足其中一种形式。


现在问题就变成给定 \(O(n)\) 条带权线段 \((l_i,r_i,w_i)\),每次询问给定 \(l,r\),询问 \(\min\{w_i|l\le l_i<r_i\le r\}\)
扫描线即可。

时间复杂度 \(O(n\log n)\),空间复杂度 \(O(n)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
constexpr int N=3e5+10;
int n,q,x[N],w[N],l,r;int64 ans[N];
int tp,stk[N],L[N],R[N];vector<array<int64,3>> vec[N];
int64 c[N];
void upd(int pos,int64 val){for(pos=n-pos+1;pos<=n;pos+=pos&-pos)chkMin(c[pos],val);}
int64 ask(int pos){int64 ans=INF64;for(pos=n-pos+1;pos;pos-=pos&-pos)chkMin(ans,c[pos]);return ans;}
void solve(){
	cin>>n>>q;
	forUp(i,1,n)cin>>x[i]>>w[i];
	forUp(i,1,n){
		while(tp&&w[stk[tp]]>=w[i])R[stk[tp--]]=i;
		stk[++tp]=i;
	}
	while(tp)R[stk[tp--]]=n+1;
	forDown(i,n,1){
		while(tp&&w[stk[tp]]>=w[i])L[stk[tp--]]=i;
		stk[++tp]=i;
	}
	while(tp)L[stk[tp--]]=0;
	forUp(i,1,n){
		if(R[i]!=n+1)vec[R[i]].pushb({0,i,1ll*(x[R[i]]-x[i])*(w[i]+w[R[i]])});
		if(L[i]!=0)vec[i].pushb({0,L[i],1ll*(x[i]-x[L[i]])*(w[L[i]]+w[i])});
	}
	forUp(i,1,q){
		cin>>l>>r;
		vec[r].pushb({i,l,0});
	}
	memset(c,INF,sizeof(c));
	forUp(i,1,n)for(auto [op,pos,val]:vec[i]){
		if(op==0)upd(pos,val);
		else ans[op]=ask(pos);
	}
	forUp(i,1,q)cout<<ans[i]<<'\n';
}

\(\text{Problem}13\text{ CF}2045\text{J (}2737\text{)}\)

给定一个长度为 \(n\) 的自然数序列 \(A\)
如果可以重排 \(A\),使得 \(\forall(i,j),1\le i<j\le n,A_i\oplus p\le A_j\oplus q\land A_i\oplus q\le A_j\oplus A_i\),则称 \(A\)\((p,q)\text{-xorderable}\)

另有一个长度为 \(m\) 的自然数序列 \(X\),询问 \(\sum\limits_{1\le i<j\le m}[A\text{ is }(X_i,X_j)\text{-xorderable}]\)

保证 \(2\le n,m\le2\cdot10^5,0\le A_i,X_i<2^{30}\)
\(\text{Time Limit }1\text{s},\text{Memory Limit }1024\text{MB}\)

$\text{Hint}$

考虑 \(p\oplus q\) 满足什么条件时 \(A\text{ is }(p,q)\text{-xorderable}\)

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)


有结论:\(p\oplus q\le\min\limits_{1\le i<j\le n}\{A_i\oplus A_j\}\)\(A\text{ is }(p,q)\text{-xorderable}\) 的充要条件。

$\text{Proof}$

先证必要性。

考虑从高到低按位比较,设 \(A_i,A_j,p,q\) 在该位上的值为 \(A,B,P,Q\)

  1. \(A\oplus B<P\oplus Q\),发现条件一定不满足。
  2. \(A\oplus B=P\oplus Q\),发现需要继续比较下一位。
  3. \(A\oplus B>P\oplus Q\),发现 \((A,B,P,Q)\)\((B,A,P,Q)\) 中必有一个满足。

所以,若 \(A\text{ is }(p,q)\text{-xorderable}\),则 \(\forall 1\le i<j\le n,p\oplus q\le A_i\oplus A_j\)
\(p\oplus q\le\min\limits_{1\le i<j\le n}\{A_i\oplus A_j\}\)


再证充分性。

假设我们已经知道了 \(a\)\(b\) 前面,\(b\)\(c\) 前面,我们要证的相当于 \(a\) 一定在 \(c\) 前面。

整理下条件,发现等价于已知 \(a\oplus p\le c\oplus p,a\oplus q\le c\oplus q,p\oplus q\le a\oplus c\),证明 \(a\oplus p\le c\oplus q\land a\oplus q\le a\oplus p\)

同样考虑从高到低按位考虑,设 \(a,c,p,q\) 在该位上的值为 \(A,C,P,Q\)

  1. 在最开始的几位,满足 \(A\oplus P=C\oplus P,A\oplus Q=C\oplus Q,P\oplus Q=A\oplus C\)
    此时显然有 \(A\oplus P=C\oplus Q,A\oplus Q=C\oplus P\)
  2. 不妨设最先比较出来的一位满足 \(A\oplus P<C\oplus P\),此时可以得到 \(A\oplus C=1,P=Q\)
    显然有 \(A\oplus P<C\oplus Q,A\oplus Q<C\oplus P\)
    其他两种情况不再赘述,读者仔细分析可以发现必然同时出现 \(3\) 个小于。

\(a\oplus p\le c\oplus q\land a\oplus q\le a\oplus p\)


综上所述,\(p\oplus q\le\min\limits_{1\le i<j\le n}\{A_i\oplus A_j\}\)\(A\text{ is }(p,q)\text{-xorderable}\) 的充要条件。


所以先求出 \(mn=\min\limits_{1\le i<j\le n}\{A_i\oplus A_j\}\),再求出有多少对 \((i,j)\) 满足 \(X_i\oplus X_j\le mn\) 即可。
可以用 \(01\text{-trie}\) 轻松地解决。

时间复杂度 \(O((n+m)\log V)\),空间复杂度 \(O((n+m)\log V)\)

$\text{Solution}$
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
constexpr int LEN=6e6+10;
int n,m,A,X,mn=1<<30;int64 ans;
struct Trie{
	int tot,to[LEN][2],sz[LEN];
	Trie(){tot=1;memset(to,0,sizeof(to));memset(sz,0,sizeof(sz));}
	void insert(int val){
		int cur=1;
		forDown(i,29,0){
			int c=val>>i&1;
			if(!to[cur][c])to[cur][c]=++tot;
			cur=to[cur][c],++sz[cur];
		}
	}
	int ask(int val){
		int cur=1,ans=0;
		forDown(i,29,0){
			int c=val>>i&1;
			if(to[cur][c])cur=to[cur][c];
			else cur=to[cur][c^1],ans|=1<<i;
		}
		return ans;
	}
	int query(int val,int lim){
		int cur=1,ans=0;
		forDown(i,29,0){
			int c1=val>>i&1,c2=lim>>i&1;
			if(c2)ans+=sz[to[cur][c1]],cur=to[cur][c1^1];
			else cur=to[cur][c1];
		}
		ans+=sz[cur];
		return ans;
	}
}trie[2];
void solve(){
	cin>>n>>m;
	while(n--){
		cin>>A;
		chkMin(mn,trie[0].ask(A));
		trie[0].insert(A);
	}
	while(m--){
		cin>>X;
		ans+=trie[1].query(X,mn);
		trie[1].insert(X);
	}
	cout<<ans;
}

\(\text{Problem}14\text{ CF}1578\text{B (}2947\text{)}\)

\(n\) 个等距分布在圆上的点,最初点之间都没有线段相连。
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(u,v\),用一条线段连接 \(u,v\),保证对于每个点对该操作至多执行 \(1\) 次。
  2. 给定 \(u,v\),询问 \(u,v\) 是否连通。

定义两个点 \(u,v\) 连通,当且仅当它们可以只通过线段到达,注意可以在线段的交点处拐弯。

保证 \(2\le n\le2\cdot10^5,1\le q\le3\cdot10^5,1\le u,v\le n,u\neq v\)

$\text{Hints}$
$\text{Hint}1$

考虑断环成链,发现操作没有任何变化。

$\text{Hint}2$

发现两个连通块之间要么不交要么包含,考虑在每个连通块内只保留向下一个点的边。

$\text{Hint}3$

发现连接 \(u,v\) 相当于 \(u,v\) 不断向外层的连通块合并直至 \(u,v\) 在同一连通块。

$\text{Tutorial}$

请先阅读 \(\text{Hints}\)


\(h_i\) 为点 \(i\) 上方有多少条边。
则外层连通块可以通过向左或向右找到第一个满足 \(h_{i'}<h_i\) 的点 \(i'\)\(i'\) 所在连通块就是 \(i\) 的外层连通块。
可以通过线段树二分解决这一问题。

\(h_u\neq h_v\) 时,不妨设 \(h_u>h_v\)\(h_u<h_v\) 的情况也可类似讨论。
找到 \(u\) 的外层连通块并与 \(u\) 所在的连通块合并,并执行 \(h_u\leftarrow h_u-1\)

\(h_u=h_v\) 时,同样找到 \(u\) 的外层连通块。
若找到的位置 \(pos<v\),则合并 \(u\) 的外层连通块和 \(u\) 所在的连通块,并执行 \(h_u\leftarrow h_u-1\)
否则直接合并 \(u\)\(v\) 所在的连通块。


接下来讨论如何合并两个连通块。
首先要判断两个连通块是包含还是不交,这个可以通过维护连通块的最左点 \(L_i\) 和最右点 \(R_i\) 来解决。

若两个连通块包含,可以发现需要区间 \(h_i\leftarrow h_i-1\)
若两个连通块不交,可以发现需要区间 \(h_i\leftarrow h_i+1\)

同样用线段树维护即可。


时间复杂度 \(O(n\log n+q\alpha(n))\),空间复杂度 \(O(n)\)

$\text{Solution}$
#define forDown(i,a,b) for(int i=(a);i>=(b);--i)
template<class _Tp>void chkMax(_Tp &x,const _Tp &y){x<y?x=y:0;}
template<class _Tp>void chkMin(_Tp &x,const _Tp &y){x>y?x=y:0;}
constexpr int N=2e5+10;
int n,q,op,x,y;
struct DSU{
	int fa[N],sz[N],L[N],R[N];
	void init(){forUp(i,1,n)sz[fa[i]=L[i]=R[i]=i]=1;}
	int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
	void merge(int x,int y){
		x=find(x),y=find(y);
		if(sz[x]<sz[y])swap(x,y);
		fa[y]=x,sz[x]+=sz[y],chkMin(L[x],L[y]),chkMax(R[x],R[y]);
	}
	int getL(int x){return L[find(x)];}
	int getR(int x){return R[find(x)];}
}dsu;
struct segtree{
	int mn[N<<2],add[N<<2];
	segtree(){memset(mn,0,sizeof(mn));memset(add,0,sizeof(add));}
	void pushup(int rt){mn[rt]=min(mn[rt<<1],mn[rt<<1|1]);}
	void modify(int rt,int tag){mn[rt]+=tag;add[rt]+=tag;}
	void pushdown(int rt){if(add[rt]){modify(rt<<1,add[rt]);modify(rt<<1|1,add[rt]);add[rt]=0;}}
	void update(int L,int R,int val,int rt=1,int l=1,int r=n){
		if(L>R)return;
		if(L<=l&&r<=R)return modify(rt,val);
		int mid=l+r>>1;
		pushdown(rt);
		if(L<=mid)update(L,R,val,rt<<1,l,mid);
		if(mid<R)update(L,R,val,rt<<1|1,mid+1,r);
		pushup(rt);
	}
	int query(int pos,int rt=1,int l=1,int r=n){
		if(l==r)return mn[rt];
		int mid=l+r>>1;
		pushdown(rt);
		if(pos<=mid)return query(pos,rt<<1,l,mid);
		else return query(pos,rt<<1|1,mid+1,r);
	}
	int findR(int pos,int lim,int rt=1,int l=1,int r=n){
		if(pos<=l){
			if(mn[rt]>=lim)return n+1;
			if(l==r)return l;
			int mid=l+r>>1;
			pushdown(rt);
			if(mn[rt<<1]<lim)return findR(pos,lim,rt<<1,l,mid);
			else return findR(pos,lim,rt<<1|1,mid+1,r);
		}
		int mid=l+r>>1,ans=n+1;
		pushdown(rt);
		if(pos<=mid)ans=findR(pos,lim,rt<<1,l,mid);
		if(ans==n+1)ans=findR(pos,lim,rt<<1|1,mid+1,r);
		return ans;
	}
	int findL(int pos,int lim,int rt=1,int l=1,int r=n){
		if(r<=pos){
			if(mn[rt]>=lim)return 0;
			if(l==r)return l;
			int mid=l+r>>1;
			pushdown(rt);
			if(mn[rt<<1|1]<lim)return findL(pos,lim,rt<<1|1,mid+1,r);
			else return findL(pos,lim,rt<<1,l,mid);
		}
		int mid=l+r>>1,ans=0;
		pushdown(rt);
		if(mid<pos)ans=findL(pos,lim,rt<<1|1,mid+1,r);
		if(ans==0)ans=findL(pos,lim,rt<<1,l,mid);
		return ans;
	}
}T;
void merge(int x,int y){
	int Lx=dsu.getL(x),Rx=dsu.getR(x),Ly=dsu.getL(y),Ry=dsu.getR(y);
	if(Rx<Ly)T.update(Rx+1,Ly-1,1);
	else T.update(max(Lx,Ly),min(Rx,Ry),-1);
	dsu.merge(x,y);
}
void solve(){
	cin>>n>>q;dsu.init();
	while(q--){
		cin>>op>>x>>y;
		if(op==1){
			if(x>y)swap(x,y);
			int hx=T.query(x),hy=T.query(y);
			while(dsu.find(x)!=dsu.find(y)){
				if(hx>hy)merge(x,T.findR(x,hx)),--hx;
				else if(hx<hy)merge(T.findL(y,hy),y),--hy;
				else{
					int pos=T.findR(x,hx);
					if(pos<y)merge(x,pos),--hx;
					else merge(x,y);
				}
			}
		}
		if(op==2)cout<<(dsu.find(x)==dsu.find(y));
	}
}

\(\text{Problem}15\text{ CF}1386\text{C (}2452\text{)}\)

给定一个有 \(n\) 个点和 \(m\) 条边的无向图,每条边都有个编号,保证没有重边和自环。
接下来有 \(q\) 次询问,每次询问给定 \(l,r\),询问把编号在区间 \([l,r]\) 内的边删掉后是否是二分图。

保证 \(1\le n,m,q\le2\cdot10^5,1\le l\le r\le m\)
\(\text{Time Limit }1\text{s},\text{Memory Limit }256\text{MB}\)

$\text{Hints}$
$\text{Hint}1$

考虑使用断环成链时类似的 \(\text{trick}\),把前缀和后缀拼成一个完整的区间。

$\text{Hint}2$

考虑预处理 \(ans\) 为固定左端点时使图不为二分图的最左的右端点。
则当且仅当 \(ans_{r+1}\le l+m-1\) 时不是二分图。
可以发现 \(ans\) 具有单调性,考虑整体二分。

$\text{Tutorial}$

请先阅读 \(\text{Hints}\)


\((l,r,L,R)\) 表示 \(L\le ans_l\le ans_r\le R\)
\(mid=\dfrac{l+r}{2}\),则有 \((l,mid-1,L,ans_{mid})\)\((mid+1,r,ans_{mid},R)\)

考虑使用可撤销拓展域并查集来判断是否是二分图,但是每次暴力从 \(mid\) 开始扫时间复杂度无法接受。
发现若 \(r+1\le L-1\),则 \([r+1,L-1]\) 的边是始终会被扫到的,可以在上一层中保留 \([r+1,L-1]\) 的边。
具体的连边和撤销比较复杂,可以参考 \(\text{Solution}\) 中的注释。

每次的时间复杂度只与 \(r-l+1\)\(R-L+1\) 有关,显然这部分的和为 \(O(m\log m)\)
可撤销并查集的复杂度为 \(O(\log n)\)

时间复杂度 \(O(m\log m\log n+q)\),空间复杂度 \(O(n+m)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
template<class _Tp>void chkMax(_Tp &x,const _Tp &y){x<y?x=y:0;}
template<class _Tp>void chkMin(_Tp &x,const _Tp &y){x>y?x=y:0;}
constexpr int N=4e5+10;
int n,m,q,u[N],v[N],ans[N],l,r;
int fa[N],sz[N],T,tp,stkU[N],stkV[N],stkT[N];
void init(int n){forUp(i,1,n)sz[fa[i]=i]=1;}
int find(int u){return u==fa[u]?u:find(fa[u]);}
void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v)return;
	if(sz[u]<sz[v])swap(u,v);
	fa[v]=u,sz[u]+=sz[v];
	stkU[++tp]=u,stkV[tp]=v,stkT[tp]=T;
}
bool link(int u,int v){
	++T;
	if(find(u)==find(v))return true;
	merge(u,v+n);merge(v,u+n);
	return false;
}
void undo(){
	while(tp&&stkT[tp]==T){
		int u=stkU[tp],v=stkV[tp--];
		fa[v]=v,sz[u]-=sz[v];
	}
	--T;
}
void divide(int l,int r,int L,int R){
	if(l>r||L>R)return;
	if(L==R){
		forUp(pos,l,r)ans[pos]=L;
		return;
	}
	int pos,mid=l+r>>1;
	for(pos=mid;pos<=R;++pos){
		if(pos==r+1)chkMax(pos,L);
		if(link(u[pos],v[pos]))break;
	}// link [mid,r] and [max(r+1,L),R]
	ans[mid]=pos;
	for(;pos>=L&&pos>=mid;--pos)undo();// undo [max(mid,L),R]
	divide(l,mid-1,L,ans[mid]);
	for(pos=mid;pos<=r&&pos<=L-1;++pos)undo();// undo [mid,min(r,L-1)]
	for(pos=max(r+1,L);pos<=ans[mid]-1;++pos)link(u[pos],v[pos]);// link [max(r+1,L),ans[mid]-1]
	divide(mid+1,r,ans[mid],R);
	for(pos=max(r+1,L);pos<=ans[mid]-1;++pos)undo();// undo [max(r+1,L),ans[mid]-1]
}
void solve(){
	cin>>n>>m>>q;init(n<<1);
	forUp(i,1,m)cin>>u[i]>>v[i],u[i+m]=u[i],v[i+m]=v[i];
	divide(1,m+1,1,2*m+1);
	while(q--){
		cin>>l>>r;
		cout<<(ans[r+1]<=m+l-1?"YES\n":"NO\n");
	}
}

\(\text{Problem}16\text{ CF}2043\text{G (}3648\text{)}\)

给定一个长度为 \(n\) 的正整数序列 \(A\)
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(pos,val\),执行 \(A_{pos}\leftarrow val\)
  2. 给定 \(l,r\),询问 \(\sum\limits_{l\le i<j\le r}[A_i\neq A_j]\)

强制在线。
保证 \(1\le n\le10^5,1\le A_i,pos,val\le n,1\le l\le r\le n,1\le q\le3\cdot10^5\)
\(\text{Time Limit }8\text{s},\text{Memory Limit }1024\text{MB}\)

$\text{Hints}$
$\text{Hint}1$

正难则反。
注意到 \(\sum\limits_{l\le i<j\le r}[A_i\neq A_j]=\dfrac{(r-l+1)^2-\sum_\limits{l\le i,j\le r}[A_i=A_j]}{2}\)
考虑求出 \(\sum\limits_{l\le i,j\le r}[A_i=A_j]\)

$\text{Hint}2$

考虑对序列分块。

$\text{Tutorial}$

请先阅读 \(\text{Hints}\)


我们需要计算块内、散块对散块、散块对整块、整块对整块的贡献。


考虑如何计算块内的贡献。

对于散块,我们可以直接开桶计算贡献。
对于整块,我们考虑维护 \(cnt_{val,bid}=\sum[pos\in\text{block1}_{bid}\land A_{pos}=val],res_{bid}=\sum cnt_{val,bid}^2\)
直接对 \(res\) 求和计算贡献即可。

时间复杂度 \(O(n)-O(1)-O(S_1+\dfrac{n}{S_1})\)


考虑如何计算散块对散块的贡献。

先对一个散块开桶计数,遍历另一个散块计算贡献即可。

时间复杂度 \(O(S_1)\)


考虑如何计算散块对整块的贡献。

考虑维护 \(pre_{val,bid}=\sum_{bid'=1}^{bid}cnt_{val,bid'}\)
直接遍历散块,对于每一个数用 \(pre\) 计算贡献。

时间复杂度 \(O(\dfrac{n^2}{S_1})-O(\dfrac{n}{S_1})-O(S_1)\)


考虑如何计算整块对整块的贡献。

考虑维护 \(f_{i,j}=[i\neq j]\sum cnt_{val,i}cnt_{val,j}\)
发现修改是做 \(O(\dfrac{n}{S_1})\) 次单点修改,查询是查子矩阵和。

考虑对 \(f\) 的行和列再做一次分块。
考虑维护 \(rf_{i,bid}=\sum[pos\in\text{block2}_{bid}]f_{i,pos},cf_{i,bid}=\sum[pos\in\text{block2}_{bid}]f_{pos,i}\)
以及 \(rcf_{bid_1,bid_2}=\sum[pos_1\in\text{block2}_{bid_1}\land pos_2\in\text{block2}_{bid_2}]f_{pos_1,pos_2}\)

修改时直接做 \(O(\dfrac{n}{S_1})\) 次单点修改即可。
查询时对行的散块用 \(f\)\(rf\) 计算贡献,对行的整块用 \(cf\)\(rcf\) 计算贡献。

时间复杂度 \(O(\dfrac{n^2}{S_1})-O(\dfrac{n}{S_1})-O((S_2+\dfrac{n}{S_1S_2})^2)\)


\(S_1=O(n^{\frac{1}{2}}),S_2=O(n^{\frac{1}{4}})\) 时取到最优复杂度。

时间复杂度 \(O(n\sqrt{n})-O(q\sqrt{n})-O(q\sqrt{n})\),空间复杂度 \(O(n\sqrt{n})\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=1e5+10,B1=317+10,B2=18+10;
int n,q,A[N],op,pos,val,l,r;int64 ans;
int get(int x){return (x+ans)%n+1;}
int S1,S2,tot1,tot2,L1[B1],R1[B1],blng1[N],L2[B2],R2[B2],blng2[B1],cnt[N][B1],pre[N][B1],res[B1],f[B1][B1],rf[B1][B2],cf[B1][B2],rcf[B2][B2],tmp[N];
void build(){
	S1=sqrt(n),tot1=(n-1)/S1+1;
	S2=sqrt(tot1),tot2=(tot1-1)/S2+1;
	L1[0]=1-S1;forUp(i,1,tot1){
		L1[i]=L1[i-1]+S1,R1[i]=min(R1[i-1]+S1,n);
		forUp(pos,L1[i],R1[i])blng1[pos]=i;
	}
	L2[0]=1-S2;forUp(i,1,tot2){
		L2[i]=L2[i-1]+S2,R2[i]=min(R2[i-1]+S2,tot1);
		forUp(pos,L2[i],R2[i])blng2[pos]=i;
	}
	forUp(i,1,n){
		res[blng1[i]]-=cnt[A[i]][blng1[i]]*cnt[A[i]][blng1[i]];
		++cnt[A[i]][blng1[i]];
		res[blng1[i]]+=cnt[A[i]][blng1[i]]*cnt[A[i]][blng1[i]];
		forUp(j,blng1[i],tot1)++pre[A[i]][j];
	}
	forUp(i,1,tot1)forUp(j,1,tot1){
		if(i==j)continue;
		forUp(pos,L1[i],R1[i])f[i][j]+=cnt[A[pos]][j];
		rf[i][blng2[j]]+=f[i][j];
		cf[j][blng2[i]]+=f[i][j];
		rcf[blng2[i]][blng2[j]]+=f[i][j];
	}
}
void update(int pos,int val){
	res[blng1[pos]]-=cnt[A[pos]][blng1[pos]]*cnt[A[pos]][blng1[pos]];
	cnt[A[pos]][blng1[pos]]+=val;
	res[blng1[pos]]+=cnt[A[pos]][blng1[pos]]*cnt[A[pos]][blng1[pos]];
	forUp(i,blng1[pos],tot1)pre[A[pos]][i]+=val;
	forUp(j,1,tot1){
		int i=blng1[pos];
		if(i==j)continue;
		f[i][j]+=val*cnt[A[pos]][j];
		rf[i][blng2[j]]+=val*cnt[A[pos]][j];
		cf[j][blng2[i]]+=val*cnt[A[pos]][j];
		rcf[blng2[i]][blng2[j]]+=val*cnt[A[pos]][j];
	}
	forUp(i,1,tot1){
		int j=blng1[pos];
		if(i==j)continue;
		f[i][j]+=val*cnt[A[pos]][i];
		rf[i][blng2[j]]+=val*cnt[A[pos]][i];
		cf[j][blng2[i]]+=val*cnt[A[pos]][i];
		rcf[blng2[i]][blng2[j]]+=val*cnt[A[pos]][i];
	}
}
int64 query(int l,int r){
	int lb1=blng1[l],rb1=blng1[r];int64 ans=0;
	if(lb1==rb1){
		forUp(pos,l,r){
			ans-=tmp[A[pos]]*tmp[A[pos]];
			++tmp[A[pos]];
			ans+=tmp[A[pos]]*tmp[A[pos]];
		}
		forUp(pos,l,r)--tmp[A[pos]];
		return ans;
	}
	forUp(pos,l,R1[lb1]){
		ans-=tmp[A[pos]]*tmp[A[pos]];
		++tmp[A[pos]];
		ans+=tmp[A[pos]]*tmp[A[pos]];
	}
	forUp(pos,l,R1[lb1])--tmp[A[pos]];
	forUp(pos,L1[rb1],r){
		ans-=tmp[A[pos]]*tmp[A[pos]];
		++tmp[A[pos]];
		ans+=tmp[A[pos]]*tmp[A[pos]];
	}
	forUp(pos,L1[rb1],r)--tmp[A[pos]];
	forUp(bid,lb1+1,rb1-1)ans+=res[bid];
	forUp(pos,l,R1[lb1])++tmp[A[pos]];
	forUp(pos,L1[rb1],r)ans+=tmp[A[pos]]<<1;
	forUp(pos,l,R1[lb1])--tmp[A[pos]];
	forUp(pos,l,R1[lb1])ans+=pre[A[pos]][rb1-1]-pre[A[pos]][lb1]<<1;
	forUp(pos,L1[rb1],r)ans+=pre[A[pos]][rb1-1]-pre[A[pos]][lb1]<<1;
	if(rb1-lb1>1){
		int lb2=blng2[lb1+1],rb2=blng2[rb1-1];
		if(lb2==rb2){
			forUp(r,lb1+1,rb1-1)forUp(pos,lb1+1,rb1-1)ans+=f[r][pos];
			return ans;
		}
		forUp(i,lb1+1,R2[lb2]){
			forUp(pos,lb1+1,R2[lb2])ans+=f[i][pos];
			forUp(pos,L2[rb2],rb1-1)ans+=f[i][pos];
			forUp(bid,lb2+1,rb2-1)ans+=rf[i][bid];
		}
		forUp(i,L2[rb2],rb1-1){
			forUp(pos,lb1+1,R2[lb2])ans+=f[i][pos];
			forUp(pos,L2[rb2],rb1-1)ans+=f[i][pos];
			forUp(bid,lb2+1,rb2-1)ans+=rf[i][bid];
		}
		forUp(rbid,lb2+1,rb2-1){
			forUp(pos,lb1+1,R2[lb2])ans+=cf[pos][rbid];
			forUp(pos,L2[rb2],rb1-1)ans+=cf[pos][rbid];
			forUp(bid,lb2+1,rb2-1)ans+=rcf[rbid][bid];
		}
	}
	return ans;
}
void solve(){
	cin>>n;
	forUp(i,1,n)cin>>A[i];
	cin>>q;
	build();
	while(q--){
		cin>>op;
		if(op==1){
			cin>>pos>>val;pos=get(pos),val=get(val);
			update(pos,-1);
			A[pos]=val;
			update(pos,1);
		}
		if(op==2){
			cin>>l>>r;l=get(l),r=get(r);if(l>r)swap(l,r);
			ans=(r-l+1ll)*(r-l+1ll)-query(l,r)>>1;
			cout<<ans<<' ';
		}
	}
}

\(\text{Problem}17\text{ CF}1718\text{F (}3749\text{)}\)

给定一个长度为 \(n\),值域为 \([1,m]\) 的正整数序列 \(A\),以及一个正整数 \(V\)
接下来有 \(q\) 次询问,每次询问给定 \(l,r\),询问 \(\sum\limits_{d=1}^V[\gcd(d,\prod\limits_{i=l}^rA_i)=1]\)

保证 \(1\le n,q\le10^5,1\le m\le2\cdot10^4,1\le V\le10^5,1\le l\le r\le n\)
\(\text{Time Limit }3\text{s},\text{Memory Limit }256\text{MB}\)

$\text{Hints}$
$\text{Hint}1$

\(A_{l\sim r}\) 的质因子集合为 \(S\)

\(ans=\sum\limits_{T\subset S}(-1)^{|T|}\left\lfloor\dfrac{V}{\prod\limits_{x\in T}x}\right\rfloor\)

$\text{Hint}2$

考虑对质因子大小进行分治。

$\text{Tutorial}$

请先阅读 \(\text{Hints}\)


首先把质数分为 \(\le\sqrt{V}\)\(>\sqrt{V}\) 两类。

小质数只有 \(65\) 个,记忆化跑 \(\text{DFS}\) 时间复杂度可以接受(有效的子集数只有 \(14920\) 个)。
可以对小质数跑前缀和,来判断一个区间里是否存在一个小质数。

考虑计算大质数的贡献。

对于一个大质数 \(p\),其贡献为 \(\sum_\limits{1\le i\le \lfloor\frac{V}{p}\rfloor}[i\text{ is not a multiple of a small prime}]\)
考虑用莫队跑满足 \(\lfloor\dfrac{V}{p}\rfloor=x\) 的大质数 \(p\) 有多少个,再用小质数筛掉不应计入贡献的位置即可。

时间复杂度 \(O(V+\dfrac{n\sqrt{V}}{\ln V}+n\sqrt{q}+q\sqrt{V})\),空间复杂度 \(O(\dfrac{n\sqrt{V}}{\ln V}+q)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
using int128=__int128;
constexpr int N=1e5+10,B=316,smallP=65;
int n,m,V,q,A[N],ans[N];struct Query{int l,r,id;}query[N];
int pcnt,P[N];bool np[N];
void sieve(int n=N-10){
	forUp(i,2,n){
		if(!np[i])P[++pcnt]=i;
		for(int j=1;j<=pcnt&&i*P[j]<=n;++j){
			np[i*P[j]]=true;
			if(i%P[j]==0)break;
		}
	}
}
int tot,tmp[smallP+1];map<int128,int> mp;
int dfs(int pos,int mul,int val){
	if(pos>tot)return V/mul*val;
	int res=0;
	if(tmp[pos]<=V/mul)res+=dfs(pos+1,mul*tmp[pos],-val);
	res+=dfs(pos+1,mul,val);
	return res;
}
int pre[smallP+1][N],cnt[N],bigP[B+10],unbanned[B+10];
void add(int val){
	if(val==1)return;
	if(cnt[val]++==0)++bigP[V/val];
}
void del(int val){
	if(val==1)return;
	if(--cnt[val]==0)--bigP[V/val];
}
void solve(){
	cin>>n>>m>>V>>q;
	forUp(i,1,n)cin>>A[i];
	forUp(i,1,q)cin>>query[i].l>>query[i].r,query[i].id=i;
	sieve();
	forUp(i,1,n)forUp(pid,1,smallP){
		pre[pid][i]=pre[pid][i-1]+(A[i]%P[pid]==0);
		while(A[i]%P[pid]==0)A[i]/=P[pid];
	}
	sort(query+1,query+q+1,[&](const Query &lhs,const Query &rhs)->bool{return lhs.l/B==rhs.l/B?lhs.l/B&1?lhs.r<rhs.r:lhs.r>rhs.r:lhs.l<rhs.l;});
	for(int l=1,r=0,i=1;i<=q;++i){
		int ql=query[i].l,qr=query[i].r,qid=query[i].id;
		while(ql<l)add(A[--l]);
		while(r<qr)add(A[++r]);
		while(l<ql)del(A[l++]);
		while(qr<r)del(A[r--]);
		forUp(i,1,B)unbanned[i]=1;
		int128 S=0;tot=0;
		forUp(pid,1,smallP)if(pre[pid][l-1]!=pre[pid][r]){
			S|=(int128)(1)<<(pid-1);
			tmp[++tot]=P[pid];
			for(int num=P[pid];num<=B;num+=P[pid])unbanned[num]=0;
		}
		int res;
		if(mp.find(S)==mp.end())mp[S]=res=dfs(1,1,1);
		else res=mp[S];
		forUp(num,1,B){
			unbanned[num]+=unbanned[num-1];
			res-=bigP[num]*unbanned[num];
		}
		ans[qid]=res;
	}
	forUp(i,1,q)cout<<ans[i]<<'\n';
}

\(\text{Problem}18\text{ ABC}434\text{G (}3502\text{)}\)

对于一个只包含 \(\texttt{0}\sim\texttt{9}\)\(\texttt{B}\) 的字符串 \(S\),定义 \(f(S)\) 为通过下述过程得到的字符串:

初始有一个空的字符串 \(T\)
\(S\) 的每个字符 \(S_i\) 依次进行下述操作:

  • \(S_i=\texttt{0}\sim\texttt{9}\),则把 \(S_i\) 追加到 \(T\) 的末尾。
  • \(S_i=\texttt{B}\),则删除 \(T\) 的最后一个字符。若 \(T\) 为空则不进行任何操作。

完成所有操作后,记 \(T\)\(f(S)\)

给定一个长度为 \(n\) 且只包含 \(\texttt{0}\sim\texttt{9}\)\(\texttt{B}\) 的字符串 \(S\)
接下来有 \(q\) 次操作,有以下 \(2\) 种类型:

  1. 给定 \(pos,c\),执行 \(S_{pos}\leftarrow c\)
  2. 给定 \(l,r\),令 \(T=f(S_{l\sim r})\)
    \(T\) 为空串,则回答 \(-1\);否则回答视其为十进制整数后对 \(998244353\) 取模的结果。

保证 \(1\le n\le8\cdot10^6,1\le q\le2\cdot10^5,1\le pos\le n,1\le l\le r\le n\)
\(\text{Time Limit }3\text{s},\text{Memory Limit }1024\text{MB}\)

$\text{Hint}$

发现难以有 \(O(1)\)\(\text{pushup}\)
考虑使用单侧递归以达到 \(O(\log n)\)\(\text{pushup}\)

$\text{Tutorial}$

请先阅读 \(\text{Hint}\)


首先,我们考虑维护一段区间内进行操作后前面有几个 \(\texttt{B}\),后面数字的长度以及对 \(998244353\) 取模的结果。
记这几个信息分别为 \(B,len,val\)

\(\text{pushup}\) 的时候,发现需要把 \(lson\) 后面加了 \(rson_{B}\)\(\text{B}\) 的信息与 \(rson\) 进行合并。
而查询时,也需要这种某个结点后面加了 \(k\)\(\text{B}\) 的信息。

记在 \(rt\) 结点后面加了 \(k\)\(\texttt{B}\) 的信息为 \(\operatorname{ask}(rt,k)\)
\(k\le rson_{len}\),则只需要 \(\operatorname{ask}(rson,k)\) 的信息;
否则,只需要 \(\operatorname{ask}(lson,k-rson_{len}+rson_{B})\) 的信息。
发现这是个单侧递归,因此问题已经解决了。

时间复杂度 \(O(n\log n)-O(q\log^2n)-O(q\log^2n)\),空间复杂度 \(O(n)\)

$\text{Solution}$
#define forUp(i,a,b) for(int i=(a);i<=(b);++i)
constexpr int N=8e6+10,mod=998244353;
int n,q,pow10[N],inv10[N];string s;
int tot;array<int,3> tmp[N];
struct node{
	int len,val,B;
	node(char c='0'){
		if(c=='B')len=0,val=0,B=1;
		else len=1,val=c-'0',B=0;
	}
}T[N<<2];
node ask(int rt,int l,int r,int B){
	if(l==r){
		node ans=T[rt];
		if(B==0)return ans;
		else{
			if(ans.len)ans.len=ans.val=0,--B;
			ans.B+=B;
			return ans;
		}
	}
	int mid=l+r>>1;
	if(B<=T[rt<<1|1].len){
		node res=ask(rt<<1|1,mid+1,r,B);
		node ans=T[rt];
		ans.len-=B;
		ans.val=1ll*(ans.val-T[rt<<1|1].val+mod)*inv10[T[rt<<1|1].len]%mod;
		ans.val=(1ll*ans.val*pow10[res.len]+res.val)%mod;
		return ans;
	}
	else return ask(rt<<1,l,mid,B-T[rt<<1|1].len+T[rt<<1|1].B);
}
void pushup(int rt,int l,int r){
	int mid=l+r>>1;
	node ans=ask(rt<<1,l,mid,T[rt<<1|1].B);
	ans.len+=T[rt<<1|1].len;
	ans.val=(1ll*ans.val*pow10[T[rt<<1|1].len]+T[rt<<1|1].val)%mod;
	T[rt]=ans;
}
void build(int rt,int l,int r){
	if(l==r){T[rt]=node(s[l]);return;}
	int mid=l+r>>1;
	build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
	pushup(rt,l,r);
}
void update(int rt,int l,int r,int pos,char c){
	if(l==r){T[rt]=node(c);return;}
	int mid=l+r>>1;
	if(pos<=mid)update(rt<<1,l,mid,pos,c);
	else update(rt<<1|1,mid+1,r,pos,c);
	pushup(rt,l,r);
}
void query(int rt,int l,int r,int L,int R){
	if(L<=l&&r<=R){tmp[++tot]={rt,l,r};return;}
	int mid=l+r>>1;
	if(mid<R)query(rt<<1|1,mid+1,r,L,R);
	if(L<=mid)query(rt<<1,l,mid,L,R);
}
void solve(){
	cin>>n>>q>>s;s=" "+s;
	pow10[0]=inv10[0]=1;forUp(i,1,n)pow10[i]=10ll*pow10[i-1]%mod,inv10[i]=299473306ll*inv10[i-1]%mod;
	build(1,1,n);
	while(q--){
		int op;cin>>op;
		if(op==1){
			int pos;char c;cin>>pos>>c;
			update(1,1,n,pos,c);
		}
		if(op==2){
			int l,r;cin>>l>>r;
			tot=0;query(1,1,n,l,r);
			int len=0,val=0,B=0;
			forUp(i,1,tot){
				node ans=ask(tmp[i][0],tmp[i][1],tmp[i][2],B);
				B=ans.B;
				val=(val+1ll*ans.val*pow10[len])%mod;
				len+=ans.len;
			}
			if(len==0)cout<<"-1\n";
			else cout<<val<<'\n';
		}
	}
}
posted @ 2025-10-24 16:21  LXcjh4998  阅读(28)  评论(0)    收藏  举报