4246
myee
2021-10-31 18:06:18
### 前言
可能大多数人看到这题就一眼动态图连通性秒了。
但是我发现自己不会敲动态图连通性……
于是,提供一种离线做法。
什么,LCT?
LCT 多难打,放一个**更加暴力而简约**的离线做法。
---
### 思路
线段树分治。
考虑到每条道路都有一个**寿命**,即从某时刻到某时刻里存在,我们具备了线段树分治的一个先决条件。**特别的,如果最后都没有死,我们最后杀掉它。**
我们维护一个**时光序列**,即某时刻中有的道路,那么对每条道路,我们**在时光序列的某段中同时插入**,最后对时光序列每个询问点进行查询即可,使用并查集复杂度是 $O(nm\alpha(n))$ 的。
~~这不就暴力吗。~~
考虑到时光序列可以形如一颗 Leafy Tree(把实际信息存在叶子节点的树)的,我们用线段树维护它,插入边时就是在线段树上区间插入,查询时 dfs 一遍线段树即可。
什么,你问我 dfs 时怎么统计贡献?
使用带撤销并查集即可。
什么,你不会带撤销并查集?
其实就是不用路径压缩而用其它优化(如按秩合并)的并查集,开栈记录下 `merge` 过程,撤销操作就取栈顶回退即可。
复杂度 $O(n+m\log m\log n)$。
---
### Code
带撤销并查集使用按秩合并没有精神,以下代码利用 Treap 的思想,封装了一个 `Heap_DSU`。
```cpp
#include <algorithm>
#include <map>
#include <stdio.h>
#include <vector>
typedef long long llt;
typedef unsigned uint;typedef unsigned long long ullt;
typedef bool bol;typedef char chr;typedef void voi;
typedef double dbl;
template<typename T>bol _max(T&a,T b){return(a<b)?a=b,true:false;}
template<typename T>bol _min(T&a,T b){return(b<a)?a=b,true:false;}
template<typename T>T power(T base,T index,T mod){return((index<=1)?(index?base:1):(power(base*base%mod,index>>1,mod)*power(base,index&1,mod)))%mod;}
template<typename T>T lowbit(T n){return n&-n;}
template<typename T>T gcd(T a,T b){return b?gcd(b,a%b):a;}
template<typename T>T lcm(T a,T b){return(a!=0||b!=0)?a/gcd(a,b)*b:(T)0;}
template<typename T>T exgcd(T a,T b,T&x,T&y){if(!b)return y=0,x=1,a;T ans=exgcd(b,a%b,y,x);y-=a/b*x;return ans;}
template<typename T,typename Rand>
class Heap_DSU
{
private:
std::vector<T>Fath,Prior,Hty;
T root(T p){return(p==Fath[p])?p:root(Fath[p]);}
public:
voi build(T n,Rand rng=Rand())
{
Fath.resize(n),Prior.resize(n),Hty.clear();
for(T i=0;i<n;i++)Fath[i]=i,Prior[i]=rng();
}
bol connected(uint u,uint v){return root(u)==root(v);}
bol merge(T u,T v)
{
if((u=root(u))==(v=root(v)))return Hty.push_back(u),false;
if(Prior[u]<Prior[v])std::swap(u,v);
return Fath[u]=v,Hty.push_back(u),true;
}
bol revoke()
{
if(Hty.empty())return false;
T p=Hty.back();Hty.pop_back();
if(Fath[p]==p)return false;
return Fath[p]=p,true;
}
};
typedef std::pair<uint,uint>Pair;
struct Seg
{
std::vector<Pair>Way;uint len;Seg*L,*R;
voi build(uint n){if((len=n)>1)L=new Seg,R=new Seg,L->build(len>>1),R->build(len-(len>>1));}
voi insert(uint l,uint r,Pair p)
{
if(!l&&r==len){Way.push_back(p);return;}
if(l<(len>>1))
if(r<=(len>>1))L->insert(l,r,p);
else L->insert(l,len>>1,p),R->insert(0,r-(len>>1),p);
else R->insert(l-(len>>1),r-(len>>1),p);
}
};
ullt Ra=1;const ullt Rb=10007,Rc=114513;struct rng{uint operator()(){return Ra=Ra*Rb+Rc;}};
Heap_DSU<uint,rng>U;
chr C[10];Pair P[114514];uint Op[114514];
bol Ans[114514];uint t=0;
uint hash(uint a,bol b){return a<<1|b;}
std::map<Pair,uint>M;Seg S;
voi dfs(Seg*S,uint cnt)
{
uint k=0;for(auto w:S->Way)U.merge(w.first,w.second),k++;
if(S->len==1)
{
if(!Op[cnt])Ans[t++]=U.connected(P[cnt].first,P[cnt].second);
}
else dfs(S->L,cnt),dfs(S->R,cnt+S->L->len);
while(k--)U.revoke();
}
int main()
{
uint n,m=0,r,c;scanf("%u%u",&n),U.build(n<<1);
while(scanf("%s",C)==1&&*C!='E')
{
Op[m]=*C=='A'?0:(*C=='O'?1:2);
scanf("%u%u",&r,&c),P[m].first=hash(c-1,r-1);
scanf("%u%u",&r,&c),P[m].second=hash(c-1,r-1);
if(P[m].first>P[m].second)std::swap(P[m].first,P[m].second);
m++;
}
S.build(m);
for(uint i=0;i<m;i++)if(Op[i]==1)M[P[i]]=i;else if(Op[i]==2)S.insert(M[P[i]],i,P[i]),M.erase(P[i]);
for(auto w:M)S.insert(w.second,m,w.first);
dfs(&S,0);
for(uint i=0;i<t;i++)puts(Ans[i]?"Y":"N");
return 0;
}
```
---
### 一些经验 / 非经验
可以离线(经验):
* [SP9576](https://www.luogu.com.cn/problem/SP9576)
* [SP9577](https://www.luogu.com.cn/problem/SP9577)
* [loj121](https://loj.ac/p/121)
* [P2147](https://www.luogu.com.cn/problem/P2147)
* [P3767](https://www.luogu.com.cn/problem/P3767)
强制在线(非经验):
* [loj122](https://loj.ac/p/122)
* [P5247](https://www.luogu.com.cn/problem/P5247)