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

题意

有一个 \(n\) 个点 \(m\) 条边的DAG。每个点有权值 \(a_i\)\(b_i\)。有 \(q\) 次操作,每次操作为以下其中之一:

  1. 给出 $1\ x\ y $,交换 \(a_x\)\(a_y\)
  2. 给出 $2\ x\ y $,交换 \(b_x\)\(b_y\)
  3. 给出 \(3\ x\ l\ r\),求出在 \(x\) 可达的点集 \(C\) 中,找到 \(y\in C\),满足 \(l\le a_y\le r\),求这样的 \(y\) 中最大的 \(b_y\)

保证给出的边 \((u,v)\) 满足 \(u<v\)

\(n,q\le 10^5,m\le 2\times 10^5\)

思路

首先本题的查询需要满足三个: \(x\) 可达、 \(a\in [l,r]\),以及最大的 \(b\)。其中可达性是不会改变的,其他的 \(a,b\) 值可能被修改。

首先对于DAG的可达性,不难想到用bitset去在 \(O(\frac{nm}w)\) 的时间复杂度求出。因为对于DAG,确实很难用一些其他带 $\log $ 的方式解决。

具体就是对于每一个节点 \(i\) 开一个bitset\(G\),如果 \(G_x\) 的第 \(y\) 位是1,则说明 \(x\) 可达 \(y\)。可以在DAG上用按位或求出。


接下来先考虑对于 \(a\) 的处理。

因为可达性我们已经用bitset 处理了,需要让后续的处理能与此处的bitset 快速结合。因此,此处我们同样考虑用bitset处理 \(a\)

但是 \(a\) 需要满足一个值域上的 \([l,r]\) 之间,所以考虑同样开 \(n\)bitset \(A\)。对于 \(A_i\) ,若其第 \(y\) 位为1,则说明当前 \(a_y\ge i\)

于是在 \((A_{r+1}\ \text{xor}\ A_{l})\) 中是 1 的位置对应的点的 \(a\) 值一定在 \([l,r]\) 之间。

但是,我们开不下 \(2n\) 个长为 \(n\)bitset,而且进行修改 \(a\) 时需要改 \(O(n)\)bitset,会挂掉。而可达性的bitset 又难以优化。所以考虑优化 \(A\)

我们可以考虑用分块去优化,块长为 \(T\)。对于整块去维护上述一样的后缀,散块暴力求值即可。这样就只用开 \(\frac nT\)bitset了。

进行修改时只用修改 $\frac{n}T $ 个块。而求在 \([l,r]\)\(A\) 也只需 \(O(\frac nw+T)\)

我们求出了在 \([l,r]\)\(A\) 后,与 \(G_x\) 进行按位与,即可求到合法的 \(y\) 的集合 \(C\)


此时已经求出了合法的 \(y\) 的集合。考虑如何在这个用bitset 表示的集合中找出最大的 \(b_y\)

这里还可以再用bitset去与 \(a\) 一样维护 \(b\) 值,设其为 \(B\)。这样修改也可以 \(O(\frac nT)\)

我们需要找到 \(C\)\(b\) 值最大的值所在的块,也就是与 \(C\) 进行按位与后存在 1 位的最大的 \(B\) 的块。然后再暴力在块中找到最大的且在 \(C\) 中的值,输出即可。

但是这里找最大的 \(B\) 块不能直接枚举每个块再进行按位与,因为这样单次是 \(O(\frac {n^2} {Tw})\),完全过不了。

对于两个bitset进行按位与是 \(O(\frac nw)\),但只对一位求按位与就只需 \(O(1)\)

我们不妨开一个指针 \(j\),初始化为 \(B\) 的第一个块。在对于 \(C\) 的每一位进行判断,如果 \(B_j\)\(C\) 按位与后这一位是 1,就不断让 \(j\) 加一,直到这一位结果不为 1

这样最后的 \(B\) 块就是 \(B_{j-1}\)。这样做时间复杂度是 \(O(\frac nw+\frac nT)\)。然后再在块中暴力找在 \(C\) 中的最大值,输出即可。

这道题就做完了,码量并不大。bitset 还是手写吧,能快点。

\(T\)\(\sqrt n\) 最优,时间复杂度 \(O(\frac {nm}w+q(\frac nw+\sqrt n))\)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int W=1570,len=320;
int n,m,q,A[N],B[N],ida[N],k,idb[N];
//a[ida[x]]=x,b[idb[x]]=x;

vector<int> e[N];
#define ull unsigned long long
struct bitst//手写bitset 
{
	ull s[W];
	void reset() {memset(s,0,sizeof(s));}
	void set(int x) {s[(x>>6)]|=1ull<<(x&63);}
	void flip(int x) {s[(x>>6)]^=1ull<<(x&63);}
	void operator &=(const bitst &b) 
	{
		for(int i=0;i<W;i++) s[i]&=b.s[i];
	}
	void operator |=(const bitst &b)
	{
		for(int i=0;i<W;i++) s[i]|=b.s[i];
	}
	void operator ^=(const bitst &b)
	{
		for(int i=0;i<W;i++) s[i]^=b.s[i];
	}
	int val(int x){ return s[(x>>6)]>>(x&63)&1;}
};
bitst g[N],a[len],b[len],c;
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int L(int x){return x*len;}
int R(int x){return min(n,x*len+len-1);}//找块的左右端点 
void solve()
{
	n=read(),m=read(),q=read();
	k=n/len;
	for(int i=1;i<=n;i++) g[i].reset(),g[i].set(i),e[i].clear();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		e[u].push_back(v);
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<e[i].size();j++) g[i]|=g[e[i][j]];//维护可达性 
	}
	for(int i=0;i<=k+1;i++) a[i].reset(),b[i].reset();
	
	for(int i=1;i<=n;i++) A[i]=read(),a[A[i]/len].set(i),ida[A[i]]=i;
	for(int i=1;i<=n;i++) B[i]=read(),b[B[i]/len].set(i),idb[B[i]]=i;
	
	for(int i=k-1;i>=0;i--) a[i]|=a[i+1],b[i]|=b[i+1];//预处理A、B bitset 
	
	while(q--)
	{
		int op=read(),x=read(),l=read(),r;
		if(op==1)
		{
			for(int i=A[x]/len;i>=0;i--) a[i].flip(x);
			for(int i=A[l]/len;i>=0;i--) a[i].flip(l);
			swap(ida[A[x]],ida[A[l]]);
			swap(A[x],A[l]);
			for(int i=A[x]/len;i>=0;i--) a[i].flip(x);
			for(int i=A[l]/len;i>=0;i--) a[i].flip(l);//修改A 
		}
		else if(op==2)
		{
			for(int i=B[x]/len;i>=0;i--) b[i].flip(x);
			for(int i=B[l]/len;i>=0;i--) b[i].flip(l);
			swap(idb[B[x]],idb[B[l]]);
			swap(B[x],B[l]);
			for(int i=B[x]/len;i>=0;i--) b[i].flip(x);
			for(int i=B[l]/len;i>=0;i--) b[i].flip(l);//修改B 
		}
		else
		{
			r=read();
			c=a[l/len];
			if(r/len<k) c^=a[r/len+1];
			for(int i=L(l/len);i<l;i++) c.flip(ida[i]);
			for(int i=R(r/len);i>r;i--) c.flip(ida[i]);
			c&=g[x];//求出可选的点集C 
			
			int pos=-1;
			for(int i=0;i<W;i++)
			{
				while(c.s[i]&b[pos+1].s[i]) pos++;
			}//找到C中b的最大值所在的块 
			if(pos==-1) 
			{
				printf("0\n");
				continue;
			}
			for(int j=R(pos);j>=L(pos);j--) //暴力在块中找 
			{
				if(c.val(idb[j])) 
				{
					printf("%d\n",j);
					break;
				}
			}
		}
	}
}
int main()
{
	int ID=read(),T=read();
	while(T--) solve();
	return 0;
}
posted @ 2025-03-20 20:02  Twilight_star  阅读(14)  评论(0)    收藏  举报