我记得很久以前写过一个离线的 O ( m l o g 2 n ) O(mlog^2n) O(mlog2n)的做法,是向线段树内添加每条边的存在时间,然后在线段树上跑,用可撤销的按秩合并并查集维护连通性。
其实离线还有一个分块做法,也用到了可撤销的并查集。如果有一系列的加边/删边操作,我们可以用set维护边的存在性,从而转化为单纯的加边操作,而单纯的加边操作可以用并查集维护连通性。我们把所有操作分块,每块
P
P
P个操作。处理第
i
i
i块时,先扫一遍块
[
1
,
i
−
1
]
[1,i-1]
[1,i−1]内的添加/删除操作,这些操作分成两类:
1.操作的边是在第
i
i
i块里没有进行添加/删除操作的边
2.操作的边是在第
i
i
i块里进行了添加/删除操作的边
直接用第一类操作构建出并查集 U U U。然后对于第 i i i块内的询问,用“第二类操作+第 i i i块内在此询问前添加/删除的操作”更新并查集,回答询问,然后将并查集撤销至 U U U的状态。
一共有
m
/
P
m/P
m/P块,考虑这样做的复杂度:
1.在每一块时重置并查集,
O
(
n
∗
m
/
P
)
O(n*m/P)
O(n∗m/P)
2.第一类操作最多
m
m
m条边,构建并查集,
O
(
m
∗
l
o
g
n
∗
m
/
P
)
O(m*logn*m/P)
O(m∗logn∗m/P)
3.最多有
m
m
m个询问,每次回答询问时,显然第二类边不超过
P
P
P条,一块内之前操作的边也不超过
P
P
P条,
O
(
m
∗
P
∗
l
o
g
n
)
O(m*P*logn)
O(m∗P∗logn)。
取 P = m P=\sqrt{m} P=m,复杂度即为 O ( m m ∗ l o g n ) O(m\sqrt{m}*logn) O(mm∗logn)。事实上我们发现1的复杂度是满的,2,3的复杂度是相互制约的,因此 P P P取得略大一点会跑得更快。
而对于这道离线的问题,我们把边的分类改成这样:
1.操作的边是在第
i
i
i块里不可能进行添加/删除操作的边
2.操作的边是在第
i
i
i块里有可能进行了添加/删除操作的边
每条边有两种可能,因此只不过是第二类边的条数上限从 P P P变成了 2 P 2P 2P,对复杂度无影响,且写起来只比离线多了一点点处理。
#define pr pair<int,int>
#define mp make_pair
#define fr first
#define sc second
const int N=200005;
const int B=5000;
int n,q;
int tp[N],ans[N];
pr ed[N][2];
set<pr>may,rep,onc;
namespace DSU
{
int f[N],sz[N],st[N][3],top,btm;
int find(int x) {while(f[x]!=x) x=f[x];return x;}
bool ask(pr E) {return find(E.fr)==find(E.sc);}
void undo( ) { while(top!=btm) sz[st[top][0]]=st[top][1],f[st[top][2]]=st[top][2],top--;}
void Ins(pr E)
{
int x=find(E.fr),y=find(E.sc);
if(x==y) return;
if(sz[x]<sz[y]) swap(x,y);
st[++top][0]=x;st[top][1]=sz[x];st[top][2]=y;
f[y]=x;sz[x]+=sz[y];
}
void Rebuild( )
{
int i,j;
top=0;
for(i=1;i<=n;i++) f[i]=i,sz[i]=1;
for(auto &x:onc) Ins(x);
btm=top;
}
}
using namespace DSU;
void addrep(pr E) {if(rep.find(E)!=rep.end( )) rep.erase(E);else rep.insert(E);}
void addonc(pr E) {if(onc.find(E)!=onc.end( )) onc.erase(E);else onc.insert(E);}
int main( )
{
int i,j,L,R,x,y;pr E;
n=read( );q=read( );
for(i=1;i<=q;i++)
{
L=i;R=min(i+B,q);i=R;
may.clear( );rep.clear( );onc.clear( );
for(j=L;j<=R;j++)
{
read(tp[j]);read(x);read(y);
if(x<y) swap(x,y);ed[j][0]=mp(x,y);
x=x%n+1;y=y%n+1;
if(x<y) swap(x,y);ed[j][1]=mp(x,y);
may.insert(ed[j][0]);may.insert(ed[j][1]);
}
for(j=1;j<L;j++)
{
if(tp[j]==2) continue;
E=ed[j][ans[j]];
if(may.find(E)!=may.end( )) addrep(E);
else addonc(E);
}
Rebuild( );
for(j=L;j<=R;j++)
{
if(tp[j]==1)
{
ans[j]=ans[j-1];E=ed[j][ans[j]];
addrep(E);
}
else
{
E=ed[j][ans[j-1]];
undo( );
for(auto &x:rep) Ins(x);
ans[j]=ask(E);
}
}
}
for(i=1;i<=q;i++)
if(tp[i]==2) printf("%d",ans[i]);
return 0;
}