题解:[省选联考 2025] 追忆

题目传送门

题意分析

下文中的集合均使用 bitset 维护。

考虑 DAG 维护连通性太难了,对于每个点维护其能够到达的点是 \(\mathcal O\left(n^2\right)\) 的,不可接受。事实上,可以证明 DAG 连通性最优只能 bitset 做到 \(\mathcal O\left(\dfrac{n^2}w\right)\)

具体而言,开 \(n\)bitset \(f_1,f_2,\cdots,f_n\)\(f_i[j]\) 表示 \(i\) 能否到达 \(j\),这个可以用 bitset 在倒序拓扑序上递推,\(\mathcal O\left(\dfrac{nm}w\right)\) 得到。

之后考虑处理 \(q\) 次询问 \(\operatorname{query}(x,l,r)\),答案就是:

\[\operatorname{query}(x,l,r)=\max(f_x\cap\set{i\mid a_i\in[l,r]}) \]

暴力做是 \(\mathcal O\left(qn\right)\) 的,不可接受。

需要优化的就是单次求解 \(\operatorname{query}(x,l,r)\)。首先对于 \(f_x\cap\set{i\mid a_i\in[l,r]}\),显然想到继续利用 bitset,让 \(f_x\) 与上另一个 bitset。可以想到把 \(\set{i\mid a_i\in[l,r]}\) 做成一个 bitset,但是 \(\mathcal O(n)\) 不能接受,所以可以考虑分块。

分块就对 \(a_i\) 值域分块,令块长为 \(B\)\(s_i=\set{i\mid a_i\in[(i-1)B+1,iB]}\)。再维护一下 \(\textit{idA}_{a_i}=i\) 就可以 \(\mathcal O\left(B+\dfrac nB\cdot\dfrac nw+\dfrac nw\right)\) 求出 \(f_x\cap\set{i\mid a_i\in[l,r]}\)

更进一步地,可以维护 \(s_i\) 的前缀和,这样求解 \(f_x\cap\set{i\mid a_i\in[l,r]}\) 的复杂度可以降到 \(\mathcal O\left(B+\dfrac nw\right)\)。修改 \(a\) 显然是 \(\mathcal O\left(\dfrac nB\right)\) 的。

\(\textit{pl}=f_x\cap\set{i\mid a_i\in[l,r]}\),接下来就是求解 \(\max(pl)\)。直接做是 \(\mathcal O(n)\) 的,即使用 bitset_Find_first_Find_next 也过不了。

\(\textit{pl}\) 里枚举 \(i\) 再求解最大的 \(b_i\) 的方法没有前途了,考虑从大到小枚举 \(b_i\),判断 \(i\) 是否在 \(\textit{pl}\) 里。维护 \(\textit{idB}_{b_i}=i\)\(n\rightarrow 1\) 暴力枚举是 \(\mathcal O(n)\) 的,考虑优化。

同样分块,维护 \(t_i=\set{i\mid b_i\in[(i-1)B+1,iB]}\)\(\operatorname{pos}(n)\rightarrow 1\) 枚举块 \(t_i\),判断 \(\textit{pl},t_i\) 是否有交,有交就在块内从大到小 \(\mathcal O(B)\) 枚举即可。

于是这一部分的复杂度从 \(\mathcal O(n)\) 变成了 \(\mathcal O\left(\dfrac nB\cdot\dfrac nw+B\right)\)。显然和 \(\mathcal O(n)\) 谁跑得快不好说,继续优化。

容易发现我们是从大到小枚举,我们只希望找到最大的 \(i\) 使得 \(t_i,\textit{pl}\) 有交。可以维护 \(t_i\) 的后缀并集(后缀和),这样就有了单调性,可以二分。

于是优化成了 \(\mathcal O\left(\dfrac nw\log\dfrac nB+B\right)\)。修改 \(b\) 的复杂度对应也是 \(\mathcal O\left(\dfrac nB\right)\)

总时间复杂度即为 \(\mathcal O\left(\dfrac{nm}w+qB+q\dfrac nw\log\dfrac nB\right)\)

发现带个 \(\log\),不好用基本不等式之类的东西求最小值,尝试 \(B=\sqrt n\),时间复杂度为 \(\mathcal O\left(\dfrac{nm}w+q\sqrt n+q\dfrac nw\log\sqrt n\right)\)。算了一下发现能过,测试一下也能过。


显然 \(\dfrac{nm}w,q\) 都没用,换而言之,我们只需要求解下式最小值:

\[f(B)=B+\dfrac nw\log_2\dfrac nB \]

\(\log_2\dfrac nB\) 不好做,将其变式:

\[\begin{aligned} f(B)&=B+\dfrac nw\left(\log_2n-\log_2 B\right)\\ &=B-\dfrac nw\log_2 B+\dfrac nw\log_2 n \end{aligned} \]

打表发现是个凸函数,实际上也是的。记 \(g(B)=B-\dfrac nw\log_2 B,f(B)=g(B)+\dfrac nw\log_2 n\)

\(g(B)\) 显然是两个凸函数相加,也是凸函数,于是 \(f(B)\) 也是凸函数。接下来求解最小值,求导:

\[\begin{aligned} f'(B)&=1-\left(\dfrac nw\cdot\log_2 B\right)'\\ &=1-\left(\dfrac nw\cdot\dfrac{\ln B}{\ln 2}\right)'\\ &=1-\dfrac n{w\ln 2}(\ln B)'\\ &=1-\dfrac n{wB\ln 2} \end{aligned} \]

因为是凸函数,所以令 \(f'(B)=0\),得 \(B=\dfrac n{w\ln 2}\) 时复杂度最优。

此时总复杂度为 \(\mathcal O\left(\dfrac{nm}w+q\dfrac n{w\ln 2}+q\dfrac nw\log(w\ln 2)\right)\)

AC 代码

取块长 \(B=\max\left(\left\lfloor\dfrac n{w\ln2}\right\rfloor,1\right)\),时间复杂度 \(\mathcal O\left(\dfrac{nm}w+q\dfrac n{w\ln 2}+q\dfrac nw\log(w\ln 2)\right)\)。因为常数的原因,和 \(B=\sqrt n\) 谁跑得快不好说。总共 3s 的差距更可能是评测机波动。

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<bitset>
using namespace std;
constexpr const int N=1e5,M=2e5,Q=1e5,P=64*log(2),V=(N+N/P-1)/(N/P);
int n,q,B,a[N+1],idA[N+1],b[N+1],idB[N+1];
bitset<N+1>f[N+1],s[V+1],t[V+1];
vector<int>g[N+1];
int pos(int x){
	return (x+B-1)/B;
}
int edgeL(int x){
	return (x-1)*B+1;
}
int edgeR(int x){
	return min(x*B,n);
}
void build(){
	static int in[N+1];
	fill(in+1,in+n+1,0);
	for(int i=1;i<=n;i++){
		for(int j:g[i]){
			in[j]++;
		}
	}
	static int q[N+1],front,rear;
	front=rear=0;
	for(int i=1;i<=n;i++){
		if(!in[i]){
			q[rear++]=i;
		}
	}
	while(front<rear){
		int x=q[front++];
		for(int v:g[x]){
			in[v]--;
			if(!in[v]){
				q[rear++]=v;
			}
		}
	}
	for(int i=rear-1;0<=i;i--){
		int x=q[i];
		f[x][x]=true;
		for(int v:g[x]){
			f[x]|=f[v];
		}
	}
	
	for(int i=1;i<=n;i++){
		s[pos(a[i])][i]=1;
		t[pos(b[i])][i]=1;
	}
	for(int i=1;i<=pos(n);i++){
		s[i]|=s[i-1];
	}
	for(int i=pos(n)-1;1<=i;i--){
		t[i]|=t[i+1];
	}
}
void clear(int n,int m,int q){
	for(int i=1;i<=n;i++){
		g[i].resize(0);
		f[i]=0;
	}
	B=max(n/P,1);
	for(int i=1;i<=pos(n);i++){
		s[i]=0;
		t[i]=0;
	}
}
void swapA(int x,int y){
	for(int i=pos(a[x]);i<=pos(n);i++){
		s[i][x]=0;
	}
	for(int i=pos(a[y]);i<=pos(n);i++){
		s[i][y]=0;
	}
	swap(a[x],a[y]);
	idA[a[x]]=x;
	idA[a[y]]=y;
	for(int i=pos(a[x]);i<=pos(n);i++){
		s[i][x]=1;
	}
	for(int i=pos(a[y]);i<=pos(n);i++){
		s[i][y]=1;
	}
}
void swapB(int x,int y){
	for(int i=pos(b[x]);1<=i;i--){
		t[i][x]=0;
	}
	for(int i=pos(b[y]);1<=i;i--){
		t[i][y]=0;
	}
	swap(b[x],b[y]);
	idB[b[x]]=x;
	idB[b[y]]=y;
	for(int i=pos(b[x]);1<=i;i--){
		t[i][x]=1;
	}
	for(int i=pos(b[y]);1<=i;i--){
		t[i][y]=1;
	}
}
int query(int x,int l,int r){
	bitset<N+1>pl;
	if(r-l+1<=2*B){
		for(int i=l;i<=r;i++){
			pl[idA[i]]=1;
		}
	}else{
		pl=s[pos(r)-1]^s[pos(l)];
		for(int i=l;i<=edgeR(pos(l));i++){
			pl[idA[i]]=1;
		}
		for(int i=edgeL(pos(r));i<=r;i++){
			pl[idA[i]]=1;
		}
	}
	pl&=f[x];
	int ans=0;
	l=1,r=pos(n);
	while(l<=r){
		int mid=l+r>>1;
		if((pl&t[mid]).any()){
			ans=mid;
			l=mid+1;
		}else{
			r=mid-1;
		}
	}
	if(!ans){
		return 0;
	}
	for(int i=edgeR(ans);edgeL(ans)<=i;i--){
		if(pl[idB[i]]){
			return i;
		}
	}
	return 0;
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int c,T;
	cin>>c>>T;
	while(T--){
		int m;
		cin>>n>>m>>q;
		clear(n,m,q);
		while(m--){
			int u,v;
			cin>>u>>v;
			g[u].push_back(v);
		}
		for(int i=1;i<=n;i++){
			cin>>a[i];
			idA[a[i]]=i;
		}
		for(int i=1;i<=n;i++){
			cin>>b[i];
			idB[b[i]]=i;
		}
		build();
		for(int i=1;i<=q;i++){
			int op,x,y,l,r;
			cin>>op>>x;
			switch(op){
				case 1:
					cin>>y;
					swapA(x,y);
					break;
				case 2:
					cin>>y;
					swapB(x,y);
					break;
				case 3:
					cin>>l>>r;
					cout<<query(x,l,r)<<'\n';
					break;
			}
		}
	}
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2026-07-30 00:22  TH911  阅读(10)  评论(0)    收藏  举报