圆方树

圆方树

图的路径信息是很难处理的,因为有环.  

如果我们需要处理的路径信息有特殊性质,可以考虑使用圆方树.  

在圆方树中,定义方点和圆点.  

其中,圆点就是原图中存在的点,编号为 $1$ ~ $\mathrm{n}$.  

方点是我们加进去的,且一个点双联通分量对应一个方点.        

这里给出具体建立圆点方点的代码:  

void tarjan(int x) {
    dfn[x]=low[x]=++scc;  
    S.push(x);  
    for(int i=hd[x];i;i=nex[i]) {
        int v=to[i]; 
        if(!dfn[v]) {
            tarjan(v);   
            low[x]=min(low[x],low[v]); 
            // x 为割点,即 x 为这个点双分量的根( DFS 树中深度最小点)   
            if(low[v]==dfn[x]) {
                int tmp; 
                ++tot;
                G[x].pb(tot);
                // 建立一个方点,将方点与处理 x 的全部连边.
                do {
                    tmp = S.top(); 
                    S.pop();  
                    G[tot].pb(tmp); 
                    se[tot].insert(val[tmp]);
                } while(tmp!=v); 
            }
        }
        else low[x]=min(low[x], dfn[v]); 
    }
}  

这里特别注意一下:若 $\mathrm{(x,y)}$ 这条边是割边,那么也同样要在 $(x,y)$ 之间建立方点.   

虽然说可能和其他的点双方点定义冲突,但是做题时发现大多数时候都不会影响.   

最后需要证明一下这个东西是一颗树,即无环且联通.  

1. 连通性好判断,因为根据定义方式肯定是联通的.

2. 因为任意两个点只能属于同一个点双,不可能两个点同时属于两个,所以无环.

 

Tourists

来源:CF487E  

建出圆方树,根据套路让每个方点仅维护儿子的最小值.

这种维护方式可以避免修改时碰到菊花图的情况.  

由于询问的是不重复点的路径最小点权,可以将每个点双想成一个环. 

显然,对于环上 $\mathrm{a,b}$ 两点,可以走两种圆弧,选最小值的圆弧.   

用一个 $\mathrm{multiset}$ 和树链剖分+线段树维护一下即可.  

#include <cstdio>
#include <vector>
#include <set>
#include <stack>
#include <cstring>
#include <algorithm>
#define N  200009
#define ll long long 
#define pb push_back
#define ls now<<1
#define rs now<<1|1
#define setIO(s) freopen(s".in","r",stdin)  
using namespace std;
const int inf=1000000009; 
stack<int>S; 
multiset<int>se[N]; 
vector<int>G[N];   
int low[N],dfn[N],scc,Q; 
int fa[N],top[N],dep[N],son[N],size[N],st[N],bu[N],mn[N<<2],cnt; 
int hd[N], to[N<<1], nex[N<<1], val[N], tot, n, m, edges, cal[N]; 
void add(int u, int v) {
    nex[++edges] = hd[u]; 
    hd[u] = edges, to[edges] = v;
}   
void tarjan(int x) {
    dfn[x]=low[x]=++scc;  
    S.push(x);  
    for(int i=hd[x];i;i=nex[i]) {
        int v=to[i]; 
        if(!dfn[v]) {
            tarjan(v);   
            low[x]=min(low[x],low[v]); 
            // x 为割点,即 x 为这个点双分量的根( DFS 树中深度最小点)   
            if(low[v]==dfn[x]) {
                int tmp; 
                ++tot;
                G[x].pb(tot);
                // 建立一个方点,将方点与处理 x 的全部连边.
                do {
                    tmp = S.top(); 
                    S.pop();  
                    G[tot].pb(tmp); 
                    se[tot].insert(val[tmp]);
                } while(tmp!=v); 
            }
        }
        else low[x]=min(low[x], dfn[v]); 
    }
}  
void dfs1(int x, int ff) {
    fa[x] = ff, size[x] = 1, dep[x] = dep[ff] + 1; 
    for(int i=0;i<G[x].size();++i) {
        int v=G[x][i]; 
        dfs1(v, x);  
        size[x] += size[v]; 
        if(size[v] > size[son[x]]) son[x] = v; 
    }
}
void dfs2(int x,int tp) {
    top[x]=tp;  
    st[x]=++cnt;  
    bu[st[x]]=x;   
    if(son[x]) {
        dfs2(son[x], tp); 
    }
    for(int i=0;i<G[x].size();++i) {
        int v=G[x][i];  
        if(v==son[x]) continue;  
        dfs2(v, v); 
    }
}
int get_lca(int x,int y) {
    while(top[x]!=top[y]) {
        if(dep[top[x]] > dep[top[y]]) swap(x,y); 
        y=fa[top[y]]; 
    }
    return dep[x]<dep[y]?x:y; 
}
void pushup(int now) {
    mn[now]=min(mn[ls], mn[rs]); 
}
void build(int l,int r,int now) {
    mn[now]=inf;
    if(l==r) {
        mn[now]=cal[bu[l]]; 
        return ; 
    }
    int mid=(l+r)>>1;  
    build(l,mid,ls); 
    build(mid+1,r,rs);   
    pushup(now); 
}
int query(int l,int r,int now,int L,int R) {
    if(l>=L&&r<=R) {
        return mn[now]; 
    }
    int mid=(l+r)>>1;  
    if(L<=mid&&R>mid) 
        return min(query(l,mid,ls,L,R), query(mid+1,r,rs,L,R)); 
    else if(L<=mid)  
        return query(l,mid,ls,L,R);
    else return query(mid+1,r,rs,L,R);  
}
void modify(int l,int r,int now,int p,int v) {
    if(l==r) {
        mn[now]=v;  
        return ; 
    }
    int mid=(l+r)>>1;  
    if(p<=mid)  modify(l,mid,ls,p,v); 
    else modify(mid+1,r,rs,p,v); 
    pushup(now); 
}   
int qpath(int x,int y) {
    int re=inf; 
    while(top[x]!=top[y]) {
        if(dep[top[x]]>dep[top[y]]) {
            swap(x,y); 
        }
        re=min(re, query(1,tot,1,st[top[y]],st[y])); 
        y = fa[top[y]]; 
    }
    if(dep[x] > dep[y]) swap(x, y); 
    return min(re, query(1, tot, 1, st[x], st[y]));  
}
int main() {
    // setIO("input");
    scanf("%d%d%d",&n,&m,&Q);  
    for(int i=1;i<=n;++i) {
        scanf("%d",&val[i]); 
    }
    for(int i=1;i<=m;++i) {
        int x,y; 
        scanf("%d%d",&x,&y);  
        add(x,y),add(y,x); 
    }
    tot=n, tarjan(1);  
    // root=1; 
    for(int i=n+1;i<=tot;++i) {
        cal[i]=(*se[i].begin());
    }
    for(int i=1;i<=n;++i) {
        cal[i]=val[i]; 
    }
    dfs1(1, 0); 
    dfs2(1, 1);  
    build(1, tot, 1); 
    for(int i=1;i<=Q;++i) {
        char op[2];
        int x,y,z;  
        scanf("%s",op);  
        if(op[0]=='C') {
            scanf("%d%d",&x,&y);  
            // val[x] -> y  
            if(fa[x]) {
                se[fa[x]].erase(se[fa[x]].find(val[x]));   
                se[fa[x]].insert(y);
                cal[fa[x]]=*se[fa[x]].begin(); 
                modify(1, tot, 1, st[fa[x]], cal[fa[x]]);   
            }
            val[x]=cal[x]=y; 
            modify(1,tot,1,st[x],cal[x]);
        }
        else {
            scanf("%d%d",&x,&y);   
            // x->y 的询问.   
            int lca=get_lca(x, y); 
            if(lca <= n) {
                printf("%d\n", qpath(x, y)); 
            }
            else {
                printf("%d\n", min(qpath(x, y), cal[fa[lca]]) ) ; 
            }
        }
    }
    return 0; 
}

  

EntropyIncreaser 与 动态图

动态求桥:

如果一条边在一个环上,则一定不是桥.  

这个用 $\mathrm{LCT}$ 打一个标记即可.  

动态求割点:

将图进行点双缩点,会形成树形结构.   

考虑如何统计对于 $\mathrm{(x,y)}$ 之间割点的答案.

每一个点双对答案的贡献就是点双在该路径上的左右两端点.   

如果是静态的,可以对于每个点双建立方点,然后连边.   

$\mathrm{(x,y)}$ 的答案就是圆点个数.  

现在由于是强制在线,所以用 $\mathrm{LCT}$ 动态维护这个过程即可.  

#include <cstdio>
#include <vector>
#include <cstring>
#include <set> 
#include <map>
#include <algorithm>
#define N  200009 
#define ll long long 
#define pb push_back 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std;
namespace A {
    #define ls s[x].ch[0] 
    #define rs s[x].ch[1] 
    struct UFS {
        int p[N]; 
        void init() {
            for(int i=0;i<N;++i) p[i]=i; 
        }
        int find(int x) { return p[x]==x?x:p[x]=find(p[x]); }  
        int merge(int x, int y) {
            x = find(x); 
            y = find(y); 
            if(x == y) return 0; 
            p[x] = y;  
            return 1;  
        }
    }T; 
    struct data {
        int ch[2], f, rev, si, v, tag;  
        data() {
            ch[0]=ch[1]=f=rev=si=v=tag=0; 
        }
    }s[N];  
    int sta[N], tot; 
    int isr(int x) { return s[s[x].f].ch[0]!=x&&s[s[x].f].ch[1]!=x; }   
    int get(int x) { return s[s[x].f].ch[1]==x; }   
    void pushup(int x) {
        s[x].si=s[ls].si+s[rs].si+s[x].v;  
    }
    void mark1(int x) {
        if(!x) return ; 
        swap(ls, rs), s[x].rev^=1; 
    }
    void mark2(int x) {
        if(!x) return ; 
        s[x].tag=1; 
        s[x].v=s[x].si=0; 
    }
    void pushdown(int x) {
        if(s[x].rev) {
            if(ls) mark1(ls); 
            if(rs) mark1(rs); 
        }
        if(s[x].tag) {
            if(ls) mark2(ls); 
            if(rs) mark2(rs); 
        }
        s[x].rev=s[x].tag=0; 
    }
    void rotate(int x) {
        int old=s[x].f,fold=s[old].f,which=get(x); 
        if(!isr(old)) 
            s[fold].ch[s[fold].ch[1]==old]=x;   
        s[old].ch[which]=s[x].ch[which^1]; 
        if(s[old].ch[which]) 
            s[s[old].ch[which]].f=old;  
        s[x].ch[which^1]=old,s[old].f=x,s[x].f=fold;  
        pushup(old),pushup(x);  
    }
    void splay(int x) {
        int v=0,u=x,fa;   
        for(sta[++v]=u; !isr(u); u = s[u].f) { 
            sta[++v] = s[u].f; 
        }
        for(;v;--v) pushdown(sta[v]); 
        for(u=s[u].f;(fa=s[x].f)!=u;rotate(x)) 
            if(s[fa].f!=u) 
                rotate(get(x)==get(fa)?fa:x);  
    }
    void access(int x) {
        for(int y=0;x;y=x,x=s[x].f) 
            splay(x),rs=y,pushup(x); 
    }
    void makert(int x) {
        access(x),splay(x),mark1(x);  
    }
    void split(int x,int y) {
        makert(x),access(y),splay(y); 
    }
    void link(int x, int y) {
        makert(x);     
        s[x].f = y; 
    }
    void ADD(int x, int y) {
        if(T.merge(x, y)) {
            ++tot; 
            s[tot].v = 1; 
            s[tot].si = 1;  
            link(x, tot);   
            link(tot, y); 
        }
        else {
            // 联通.   
            split(x, y);  
            // y is root of splay.  
            mark2(y);   
        }
    }
    int query(int x, int y) {
        if(T.find(x) != T.find(y)) {
            return -1; 
        }
        else {
            split(x, y); 
            return s[y].si;      
        }
    }
    void init(int poi) {
        tot = poi;  
    }
    #undef ls 
    #undef rs 
};
// A: 求 (x,y) 之间有几个桥.  
// B: 求 (x,y) 之间有几个割点.    
namespace B {
    #define ls s[x].ch[0] 
    #define rs s[x].ch[1] 
    struct UFS {
        int p[N]; 
        void init() {
            for(int i=0;i<N;++i) p[i]=i; 
        }
        int find(int x) { return p[x]==x?x:p[x]=find(p[x]); }  
        int merge(int x, int y) {
            x = find(x); 
            y = find(y); 
            if(x == y) return 0; 
            p[x] = y;  
            return 1;  
        }
    }T; 
    struct data {
        int ch[2],f,rev,v,si;    
        data() {
            ch[0]=ch[1]=f=rev=v=si=0; 
        }
    }s[N];  
    int sta[N], tot; 
    int isr(int x) { return s[s[x].f].ch[0]!=x&&s[s[x].f].ch[1]!=x; }   
    int get(int x) { return s[s[x].f].ch[1]==x; }          
    void mark1(int x) {
        if(!x) return ; 
        swap(ls, rs), s[x].rev^=1; 
    }
    void pushup(int x) {
        s[x].si=s[ls].si+s[rs].si+s[x].v; 
    }
    void pushdown(int x) {
        if(s[x].rev) {
            if(ls) mark1(ls); 
            if(rs) mark1(rs); 
        }
        s[x].rev = 0;   
    }
    void rotate(int x) {
        int old=s[x].f,fold=s[old].f,which=get(x); 
        if(!isr(old)) 
            s[fold].ch[s[fold].ch[1]==old]=x;   
        s[old].ch[which]=s[x].ch[which^1]; 
        if(s[old].ch[which]) 
            s[s[old].ch[which]].f=old;  
        s[x].ch[which^1]=old,s[old].f=x,s[x].f=fold;    
        pushup(old),pushup(x);  
    }
    void splay(int x) {
        int v=0,u=x,fa;   
        for(sta[++v]=u; !isr(u); u = s[u].f) { 
            sta[++v] = s[u].f; 
        }
        for(;v;--v) pushdown(sta[v]); 
        for(u=s[u].f;(fa=s[x].f)!=u;rotate(x)) 
            if(s[fa].f!=u) 
                rotate(get(x)==get(fa)?fa:x);  
    }
    void access(int x) {
        for(int y=0;x;y=x,x=s[x].f) 
            splay(x),rs=y,pushup(x); 
    }
    void makert(int x) {
        access(x),splay(x),mark1(x);  
    }
    void split(int x,int y) {
        makert(x),access(y),splay(y); 
    }
    void link(int x, int y) {
        makert(x);     
        s[x].f = y; 
    }
    void init(int poi) {
        tot = poi; 
        for(int i=1;i<=poi;++i) {
            s[i].v=s[i].si=1;  
        }
    }
    vector<int>det;  
    void dfs(int x) {
        pushdown(x);  
        if(ls) dfs(ls);  
        if(rs) dfs(rs); 
        s[x].si=s[x].v;
        det.pb(x);    
        s[x].ch[0]=s[x].ch[1]=s[x].f=s[x].rev=0;  
    }   
    void ADD(int x, int y) {
        if(T.merge(x, y)) {
            // (x, y) 之前并未联通.  
            link(x, y);  
        }
        else {
            // (x, y) 已经联通.   
            split(x, y);  
            dfs(y); 
            ++tot; 
            s[tot].v=s[tot].si=0; 
            for(int i=0;i<det.size();++i) {
                s[det[i]].f = tot;           
            }      
            det.clear();  
        }
    }
    int query(int x, int y) {
        if(T.find(x) != T.find(y)) {
            return -1; 
        }
        else {
            split(x, y); 
            return s[y].si;  
        }
    }
    #undef ls 
    #undef rs 
}; 
int main() {
    // setIO("input");   
    // freopen("input.out","w",stdout); 
    int n ,Q;   
    // 3 操作出问题了.  
    scanf("%d%d",&n,&Q);
    A::init(n);  
    B::init(n);   
    A::T.init();
    B::T.init(); 
    int lastans = 0; 
    for(int i=1;i<=Q;++i) {
        int op,x,y;
        scanf("%d%d%d",&op,&x,&y);   
        x ^= lastans; 
        y ^= lastans;   
        if(op == 1) {
            A::ADD(x, y); 
            B::ADD(x, y); 
            continue;   
        }
        int o = 0; 
        if(op == 2) {
            if(x == y) {
                printf("%d\n", o = 0); 
            }
            else {
                printf("%d\n",o = A::query(x, y));
            }  
        }
        if(op == 3) {
            if(x == y) {
                printf("%d\n", o = 1); 
            }             
            else {
                printf("%d\n",o = B::query(x, y));  
            }
        }
        lastans = (o == -1 ? lastans : o);
    }
    return 0; 
}

  

小C的独立集

仙人掌上的 DP.  

由于每条边最多只在一个环中,所以可以把环都拆出来,然后在环上 DP.  

具体做法就是边跑点双边找到环的“根”,然后拿出这个环并 DP 即可.  

#include <cstdio>
#include <vector>
#include <stack>
#include <cstring>
#include <algorithm>
#define N 100009 
#define ll long long
#define pb push_back  
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std;
const int inf=10000000;  
int n,m,scc;    
vector<int>G[N];
int low[N], dfn[N], fa[N], f[N][2];    
void calc(int x, int v) {
    int f0,f1;  
    f0=f[v][0],f1=f[v][1]; 
    for(int i=fa[v];i!=fa[x];i=fa[i]) {
        int d0 = f0; 
        int d1 = f1;  
        f0 = f[i][0] + max(d0,d1);  
        f1 = f[i][1] + d0;  
    }
    f[x][0] = f0;   

    // 强制 x 选,则 v 一定不能选.  
    f0=f[v][0],f1=-inf;        
    for(int i=fa[v];i!=fa[x];i=fa[i]) {
        int d0=f0; 
        int d1=f1; 
        f0=f[i][0]+max(d0,d1);  
        f1=f[i][1]+d0;  
    }
    f[x][1] = f1;  
}
void tarjan(int x, int ff) {
    fa[x]=ff; 
    // S.push(x); 
    low[x]=dfn[x]=++scc;   
    f[x][0] = 0, f[x][1] = 1;   
    for(int i=0;i<G[x].size();++i) {
        int v=G[x][i]; 
        if(v == ff) continue; 
        if(!dfn[v]) {
            tarjan(v, x); 
            low[x]=min(low[x], low[v]); 
        }
        else low[x]=min(low[x], dfn[v]);
        if(low[v] > dfn[x]) {
            // 这不是环上的点.  
            f[x][0] += max(f[v][0], f[v][1]); 
            f[x][1] += f[v][0]; 
        } 
    }
    for(int i=0;i<G[x].size();++i) {
        int v=G[x][i]; 
        if(fa[v] != x && dfn[v] > dfn[x]) {
            calc(x, v);  
        }
    }
}
int main() {
    // setIO("input"); 
    scanf("%d%d",&n,&m); 
    for(int i=1;i<=m;++i) {
        int x,y; 
        scanf("%d%d",&x,&y); 
        G[x].pb(y); 
        G[y].pb(x); 
    }   
    int ans = 0; 
    for(int i=1;i<=n;++i) {
        if(!dfn[i]) {
            tarjan(i, 0);
            ans += max(f[i][0], f[i][1]);  
        }
    }   
    printf("%d\n", ans); 
    return 0; 
}

  

 

posted @ 2021-09-27 10:35  guangheli  阅读(64)  评论(0)    收藏  举报