Luogu4246

前言

可能大多数人看到这题就一眼动态图连通性秒了。

但是我发现自己不会敲动态图连通性……

于是,提供一种离线做法。

什么,LCT?

LCT 多难打,放一个更加暴力而简约的离线做法。


思路

线段树分治。

考虑到每条道路都有一个寿命,即从某时刻到某时刻里存在,我们具备了线段树分治的一个先决条件。特别的,如果最后都没有死,我们最后杀掉它。

我们维护一个时光序列,即某时刻中有的道路,那么对每条道路,我们在时光序列的某段中同时插入,最后对时光序列每个询问点进行查询即可,使用并查集复杂度是 \(O(nm\alpha(n))\) 的。

这不就暴力吗。

考虑到时光序列可以形如一颗 Leafy Tree(把实际信息存在叶子节点的树)的,我们用线段树维护它,插入边时就是在线段树上区间插入,查询时 dfs 一遍线段树即可。

什么,你问我 dfs 时怎么统计贡献?

使用带撤销并查集即可。

什么,你不会带撤销并查集?

其实就是不用路径压缩而用其它优化(如按秩合并)的并查集,开栈记录下 merge 过程,撤销操作就取栈顶回退即可。

复杂度 \(O(n+m\log m\log n)\)


Code

带撤销并查集使用按秩合并没有精神,以下代码利用 Treap 的思想,封装了一个 Heap_DSU

#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;
}

一些经验 / 非经验

可以离线(经验):

强制在线(非经验):

posted @ 2022-02-14 07:28  myee  阅读(35)  评论(0编辑  收藏  举报