7.20学习日记:圆方树

7.20 学习日记 : 圆方树

前置芝士:

学习圆方树首先就要学习tarjan部分中的点双连通分量和割点等内容

割点:对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。(选自oi-wiki)

说人话呢就是说你把这个点和他的连边删掉,这个大的连通就会被分成很多小的连通图

点双连通分量:图中任意两不同点之间都有至少两条点不重复的路径。
点不重复既指路径上点不重复(简单路径),也指两条路径的交集为空(当然,路径必然都经过出发点和到达点,这不在考虑范围内)。(依旧选自oi-wiki)

我来解释!

就是图中随便选两个点,他们之间的两条简单路径互不相交,同时也意味着你随便删一个点这个图还都是连通的,这个和后面的有关

如果大家还不了解可以自行学习tarjan

来到主角:圆方树

构造过程:

圆方树 顾名思义,就是由圆点和方点组成 ,每当我们找到一个点双连通分量时(大概可以理解成一个环), 我们将这个连通块抽象成一个方点,然后将分量里的其他点作为圆点连在方点上,其次将方点连到割点上,就建成新树啦。

求割点当然需要用tarjan算法啦

luogu P4630 [APIO2018] 铁人两项

简化题意:给定一张简单无向图,问有多少对三元组 (s ,c , f)(s, c, f 互不相同)使得存在一条简单路径从 s 出发,经过 c 到达 f。

给一个圆方树题目的小trick:给点附上合适的权值

引理:对于一个点双中的两点,它们之间简单路径的并集,恰好完全等于这个点双。(需要用网络流证明,但是我不会,等我学会了我会回来更新的)

相当于考虑两圆点在圆方树上的路径,与路径上经过的方点相邻的圆点的集合,就等于原图中两点简单路径上的点集。

我们固定s , f考虑c

此时这个c就是s到f简单路径的点集

如果暴力枚举太大了咋办?

所以我们考虑给方点赋点权值为整个点双的大小,圆点为-1,这样路径上的和就是点集的个数

但是我们不知道s,f啊??

完单了吗?

并没有

我们可以借鉴树上dp , 只计算他对答案的贡献即可

贴代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 20 ; 
int n , dfn[N] , tot , m , fr , w[N] , to , cnt , sta[N] , low[N] , tp ; 
vector<int>G[N] , T[N];
typedef long long ll ; 
ll ans , num ;
void tarjan(int u , int f){
    low[u] = dfn[u] = ++tot; 
    sta[++tp] = u ;
    ++num;
    for(auto v : G[u]){
        // if( v != f ){
            if( !dfn[v] ){
                tarjan( v , u ) ; 
                low[u] = min( low[v] , low[u]);
                if( low[v] == dfn[u] ){
                    w[++cnt] = 0;
                    for (int x = 0; x != v; --tp) {
                        x = sta[tp];
                        T[cnt].push_back(x);
                        T[x].push_back(cnt);
                        ++w[cnt];
                    }
                    T[cnt].push_back(u);
                    T[u].push_back(cnt);
                    ++w[cnt];
                    }
            }
            else{
                low[u] = min( low[u] , dfn[v]);
            }
    } 
}
int sz[N] ; 
void dfs(int u , int f){
    sz[u] = ( u <= n ) ; 
    for(auto v : T[u]){
        if( v != f){ 
            dfs( v , u ) ;
            ans += 2ll * sz[v] * sz[u] * w[u];
            sz[u] += sz[v];
        }
    }
    ans += 2ll * w[u] * sz[u] * ( num - sz[u] );  
}
int main(){
    cin >> n >> m ; 
    for(int i = 1 ; i <= m ;++i){
        cin >> fr >> to ;
        G[fr].push_back(to);
        G[to].push_back(fr);
    }
    for(int i = 1 ; i <= n ;i++ ){
        w[i] = -1 ; 
    }
    cnt = n ; 
    for(int i = 1 ; i <= n ; i++ ){
        if(dfn[i] == 0 ){
            num = 0 ; 
            tarjan( i , 0 ) ; 
            --tp ; 
            dfs( i , 0 ) ;           
        }
    }
    cout << ans << endl ;
    return 0;
}

警示后人!!!!

    w[++cnt] = 0 ; 
    while(sta[tp] != u){
        T[cnt].push_back(sta[tp]);
        T[sta[tp]].push_back(cnt);
        w[cnt]++;
        tp-- ;
    }

这样退栈是错误的,为什么呢?

因为他可能会退到其他的连通分量里

正确做法

    w[++cnt] = 0;
    for (int x = 0; x != v; --tp) {
        x = sta[tp];
        T[cnt].push_back(x);
        T[x].push_back(cnt);
        ++w[cnt];
    }

68 -> 100 pts

Codeforces #487 E. Tourists

题意:

给定一张简单无向连通图,要求支持两种操作:

1.修改一个点的点权。
2.询问两点之间所有简单路径上点权的最小值。

这题挺水的,树剖+圆方树板子,操作1直接单点修改不用说 , 操作2我们直接对于每一个方点维护一个set最小值,将最小值附在方点上, 此时只需要查询方点就可以了。每次有新输入的时候直接更新。(此T我直接二十分钟写完一发直接AC)

#include<bits/stdc++.h>
using namespace std;
#define lid id << 1 
#define rid (id << 1) + 1 
const int N = 3e5 + 20 ; 
vector<int>G[N] , T[N] ; 
set<int>Z[N];
struct tree{
    int l , r , minn ; 
}tr[N << 4]; 
int dfc[N] , tot , low[N] , num , js , sta[N] , n , m  , q , fr , to , tp , w[N]; 
void tarjan(int u){
    
    low[u] = dfc[u] =++tot ; 
    sta[++tp] = u ; 
    for(auto v : G[u]){
        if(!dfc[v]){
            tarjan(v);
            low[u] = min(low[u] , low[v]);
            if( dfc[u] == low[v] ){
                ++js;
                for(int x = 0 ; x != v ; --tp){
                    x = sta[tp];
                    T[x].push_back(js);
                    T[js].push_back(x);
                }               
                T[u].push_back(js);
                T[js].push_back(u);
            }
        }  
        else{
            low[u] = min( low[u] , dfc[v] );
        }
    }
}
int dep[N] , rnk[N] , cnt ,  son[N] , dfn[N] ,  fa[N] , top[N] , sz[N] ; 
void update(int id){
    tr[id].minn = min( tr[lid].minn , tr[rid].minn ) ; 
}
void build(int id , int l , int r){
    tr[id].l = l , tr[id].r = r;
    if( l == r ){
        tr[id].minn = w[rnk[l]];
        // cout << l << " " << tr[id].minn << endl ; 
        return ; 
    }
    int mid = ( l + r ) >> 1 ; 
    build( lid , l , mid ) ;
    build( rid , mid + 1 , r );
    update(id);
}
void modify( int id , int x , int val ){
    if( tr[id].l == tr[id].r and tr[id].r == x ){
        tr[id].minn = val ; 
        return ;
    }
    int mid = ( tr[id].l + tr[id].r) >> 1 ; 
    if( x <= mid ){
        modify( lid , x , val ) ; 
    }
    else{
        modify( rid, x , val ) ; 
    }
    update(id);
}
int query( int id , int l , int r ){
    if( l <= tr[id].l and tr[id].r <= r ){
        return tr[id].minn ; 
    }
    // cerr << l << " " << r  << endl ; 
    int mid = ( tr[id].l + tr[id].r ) >> 1 ; 
    int res = 0x3f3f3f3f; 
    if( l <= mid ){
        res = min( res , query( lid , l , r )) ; 
    }
    if( r >= mid + 1 ){
        res = min( res , query( rid, l , r ) );
    }
    return res ; 
}
void dfs1( int u , int f ){
    dep[u] = dep[f] + 1 ; 
    fa[u] = f ;
    sz[u] = 1 ; 
    for(auto v : T[u]){
        if( v != f ){
            dfs1( v ,  u ) ; 
            if(sz[son[u]] < sz[v])son[u] = v ; 
            sz[u] += sz[v] ;  
        }
    }
}
void dfs2(int u , int t ){
    top[u] = t ; 
    dfn[u] = ++cnt ; 
    rnk[cnt] = u ;
    if(!son[u])return ; 
    dfs2( son[u] , t ) ;  
    for(auto v : T[u]){
        if(v != fa[u]){
            if(v != son[u])dfs2( v , v ) ; 
        }
    }    
}
int gans( int x , int y ){
    int res = 0x3f3f3f3f ; 
    while( top[x] != top[y] ){
        if(dep[top[x]] < dep[top[y]])swap(x , y);
        res = min( res , query( 1 , dfn[top[x]] , dfn[x]));
        x = fa[top[x]] ; 
    }
    if( dep[x] > dep[y])swap(x , y) ;
    res = min( res , query( 1 , dfn[x] , dfn[y])) ; 
    return res ;  
}
int lca( int x , int  y){
    while( top[x] != top[y] ){
        if(dep[top[x]] < dep[top[y]])swap(x , y);
        x = fa[top[x]] ; 
    }
    if( dep[x] > dep[y])swap(x , y) ;
    return x ;  
}
char op ; 
int main(){
    cin >> n >> m >> q ;
    for (int i = 1; i <= n; ++i) cin >> w[i];
    js = n;
    for (int i = 1; i <= m; ++i) {
        cin >> fr >> to;
        G[fr].push_back(to);
        G[to].push_back(fr);
    } 
    tarjan(1);
    dfs1(1, 0);
    // cerr << "awawa" << endl ;
    dfs2(1 ,1);
    for (int i = 1; i <= n; ++i)if (fa[i]) Z[fa[i]].insert(w[i]);
    for (int i = n + 1; i <= js; ++i) w[i] = *Z[i].begin();
    build( 1 , 1 , js ) ; 
    for(int i = 1 ; i <= q ; i++ ){
        cin >> op ;
        int a , w1 ;  
        if (op == 'C') {
            cin >> a >> w1 ; 
            modify(1, dfn[a], w1);
            if (fa[a]) {
                int u = fa[a];
                Z[u].erase(Z[u].lower_bound(w[a]));
                Z[u].insert(w1);
                if(w[u] != *Z[u].begin()) {
                    w[u] = *Z[u].begin();
                    modify(1,  dfn[u], w[u]);
                }
            }
            w[a] = w1 ; 
        }
        else{
            cin >> a >> w1 ; 
            int ans = gans( a , w1 ) ;
            // cout << ans << endl ;  
            if( lca(a , w1) > n){
                ans = min( ans , w[fa[lca(a,w1)]]) ; 
            }
            cout << ans << endl ; 
        }
    }



    return 0 ; 
}


luogu P4606 [SDOI2018] 战略游戏

题意简介:给出一个简单无向连通图。有 q 次询问:

每次给出一个点集 S(2<=|S|<=n),问有多少个点 u 满足 u 属于 S 且删掉 u 之后 S 中的点不全在一个连通分量中。

其实就是求这个点集里面有多少个割点

我们怎么求呢?

将圆点与方点之间边的权值设为1,此时两个点之间的圆点个数等于两个点之间的距离

把 S 中的点按照 DFS 序排序,计算排序后相邻两点的距离和(还包括首尾两点之间的距离),答案就是距离和的一半,因为每条边只被经过两次。

当节点最浅的那个点是圆点的时候答案需要+1

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 20 ; 
int t , q , qs  , n , m , fr , to ,dfn[N] , tot , low[N] , cnt , sta[N] , tp ; 
vector<int>G[N],T[N];
void tarjan(int u){
    dfn[u] = low[u] = ++tot ; 
    //  ; 
    sta[++tp] = u ; 
    for(auto v : G[u]){
        // cout << v << endl ; 
        if(!dfn[v]){
            tarjan(v);
            low[u] = min( low[u] , low[v] );
            if( low[v] == dfn[u] ){
                cnt++;
                for(int x = 0 ; x != v ; tp--){
                    x = sta[tp];
                    T[x].push_back(cnt);
                    T[cnt].push_back(x);
                }
                T[u].push_back(cnt);
                T[cnt].push_back(u);
            }
        }
        else{
            low[u] = min( low[u] , dfn[v] ) ; 
        }
    }
}
int dis[N] , fa[N] , dep[N] , top[N] , son[N] ,  dfn1[N] , sz[N]; 

void dfs1(int u , int f){
    fa[u] = f ; 
    dep[u] = dep[f] + 1 ; 
    sz[u] = 1 ; 
    for(auto v : T[u]){
        if( v != f ){
            dfs1( v , u ) ; 
            sz[u] += sz[v] ; 
            if( sz[son[u]] < sz[v])son[u] = v ; 
        }
    }
}
int num ; 
void dfs2( int u , int t ){
    dfn1[u] = ++num ; 
    top[u] = t ; 
    dis[u] = dis[fa[u]] + ( u <= n );
    if(!son[u])return;
    dfs2( son[u] , t ) ; 
    for(auto v : T[u]){
        if( v != fa[u] and v != son[u] ){
            dfs2( v , v ) ; 
        }
    }
}
int lca( int a , int b ){
    while(top[a]!=top[b]){
        if( dep[top[a]] < dep[top[b]])swap(a , b) ; 
        a = fa[top[a]] ; 
    }
    if( dep[a] > dep[b] )swap(a , b);
    return a ; 
}
struct dfns{
    int id , dfnid ; 
}S[N] ; 
bool cmp( dfns a1 , dfns a2){
    return a1.dfnid < a2.dfnid ; 
}
int dfc;
int dfn2[N] ;
void DFS(int u, int fz) {
  dfn2[u] = ++dfc;
  for (int v : T[u])
    if (v != fz) DFS(v, u);
}
int main(){
    // freopen("data.in","r",stdin);
    // freopen("data.out","w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin >> t ; 
    while(t--){
        tot = tp = num =  0 ; 
        cin >> n >> m ; 
        cnt = n ; 
        for(int i = 1 ; i <= max(n , m) * 2;i ++){
            G[i].clear();
            T[i].clear();
            dfn2[i] = dfn1[i] = dis[i] = fa[i] = dep[i] = top[i] = son[i] = sz[i] = low[i] = dfn[i] = sta[i] = 0 ;  
        }
        for(int i = 1 ; i <= m ; i++){
            cin >> fr >> to ; 
            G[fr].push_back(to);
            G[to].push_back(fr);
        }
        tarjan(1);
        dfs1( 1 , 0 ); 
        dfs2( 1 , 1 );
        DFS(1 , 0);
        cin >> q ; 
        for(int i = 1 ; i <= q ; i++ ){
            cin >> qs ; 
            for(int j = 1 ; j <= qs ; j++){
                cin >> S[j].id ;
                S[j].dfnid = dfn2[S[j].id];
            }
            sort( S + 1 , S + 1 + qs , cmp );
            int ans = 0 ; 
            S[qs + 1] = S[1];
            for(int j = 1 ; j <= qs ; j++ ){
                ans += ( dis[S[j].id] + dis[S[j+1].id] - 2 * dis[lca(S[j].id , S[j + 1].id)]);      
            }
            if(lca(S[1].id, S[qs].id) <= n) ans += 2;
            ans = ans - 2  * qs ;
            ans /= 2 ;  
            cout << ans << endl ; 
            for(int j = 1 ; j <= qs ; j++)S[j].id = S[j].dfnid = 0 ; 
        }
    }

    return 0;
}

警示后人

圆方树一定要开二倍,不然会Mle

posted @ 2025-07-20 21:46  Nailong2357  阅读(26)  评论(3)    收藏  举报