复习计划—双连通分量

本文介绍了如何利用割点模板解决网络连通问题,并详细讲解了通过边双缩点形成树后,运用LCA求解两点间距离的方法。还涉及了ecc缩点后的树链剖分和并查集应用,以及针对特定问题的策略调整,如关键节点的处理和树形DP算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 题目连接:https://www.luogu.com.cn/problem/P3388
  • 技巧:割点模板题
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define sz(a) (int)a.size()
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)
#define sz(a) (int)a.size()
#define pii pair <int,int>
template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 2e5 + 5;
int n , m;
int head[N] , cnt;
struct node {
    int t , next;
}edge[N << 1];
void add (int f, int t)
{
    edge[cnt].t = t;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
int dfn[N] , low[N] , cut[N], times;
void tarjan (int u , int root)
{
    dfn[u] = low[u] = ++ times;
    int tot = 0;
    for (int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if (!dfn[v])
        {
            tarjan (v, root);
            low[u] = min (low[u] , low[v]);
            if (low[v] >= dfn[u])
            {
                tot ++;
                if (u != root || tot > 1)
                    cut[u] = 1;
            }
        }
        else
            low[u] = min (low[u], dfn[v]);//这里一定要dfn[v]

    }
}
int main ()
{
    mem (head, -1);
    read (n , m);
    for (int i = 1 ; i <= m ; i ++)
    {
        int u , v;
        read (u , v);
        add (u, v) , add (v, u);
    }
    for (int i = 1 ; i <= n ; i ++)
        if (!dfn[i])
            tarjan (i, i);
    int ans = 0;
    for (int i = 1 ; i <= n ; i ++)
        if (cut[i])
            ans ++;
    cout << ans << endl;
    for (int i = 1 ; i <= n ; i ++)
        if (cut[i])
            cout << i << ' ';
    cout << endl;

}
  • 题目连接:https://www.acwing.com/problem/content/399/
  • 技巧: 常规套路,边双缩点形成树之后,利用LCA求树上两点间的距离即可
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define sz(a) (int)a.size()
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)
#define sz(a) (int)a.size()
#define pii pair <int,int>
template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 2e5 + 5;
int n , m , Q;
int head[N] , cnt;
struct node {
    int t , next;
}edge[N << 1];
void add (int f, int t)
{
    edge[cnt].t = t;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
int bridge[N << 1];
int dfn[N << 1] ,low[N << 1], times;
void tarjan(int u, int id)
{
    dfn[u] = low[u] = ++times;
    for(int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if(!dfn[v])
        {
            tarjan(v,i);
            low[u] = min (low[u] , low[v]);
            if(low[v] > dfn[u])
                bridge[i] = bridge[i^1] = 1;
        }
        else if(i != (id ^ 1)) // 如果有重边
            low[u] = min(low[u], dfn[v]);
    }
}
//处理ecc
int ecc[N << 1], type, deg[N << 1];
void dfs(int u, int block)
{
    ecc[u] = block;
    for(int i = head[u] ; i != - 1 ; i = edge[i].next)
    {
        if(!bridge[i])
        {
            int v = edge[i].t;
            if(!ecc[v])
                dfs(v,block);
        }
    }
}
vector <int> Edge[N];
int bz[N][30] , deep[N];
void dfs1(int rt, int dep)
{
    deep[rt] = dep;
    for (auto v : Edge[rt])
    {
        if(!deep[v])
        {
            bz[v][0] = rt;
            dfs1(v,dep + 1);
        }
    }
}
void deal()
{
    for (int i = 1 ; i <= 25 ; i ++)
        for (int j = 1 ; j <= n ; j ++)
            bz[j][i] = bz[bz[j][i-1]][i-1];
}
int LCA(int x,int y)
{
    if(deep[x] < deep[y])
        swap(x,y);
    int t = deep[x] - deep[y];
    for(int i = 0; (1<<i) <= t ; i ++)
        if((1<<i)&t)x=bz[x][i];
    for(int i=19 ; i >= 0 ; i--)
        if(bz[x][i] ^ bz[y][i])
            x=bz[x][i], y=bz[y][i];
    if(x==y) return x;
    return bz[x][0];
}
int main ()
{
    mem (head, -1);
    read (n , m);
    for (int i = 1 ; i <= m ; i ++)
    {
        int u , v;
        read (u , v);
        add (u , v), add (v, u);
    }
    for (int i = 1 ; i <= n ; i ++)
        if (!dfn[i])
            tarjan (i, -1);

    for (int i = 1 ; i <= n ; i ++)
        if (!ecc[i])
            dfs (i, ++ type);

    for (int i = 0 ; i < cnt ; i += 2)
    {
        int u = edge[i].t , v = edge[i + 1].t;
        if (ecc[u] != ecc[v])
            Edge[ecc[u]].pb(ecc[v]) , Edge[ecc[v]].pb(ecc[u]);
    }

    dfs1 (1, 1);

    deal ();
    read (Q);
    while (Q --)
    {
        int u , v;
        read (u, v);
        int lca = LCA (ecc[u], ecc[v]);
        //cout << ecc[u] << ' ' << ecc[v] << endl;
        write(deep[ecc[u]] + deep[ecc[v]] - 2 * deep[lca]), LF;
    }
}

  • 题目连接:https://www.acwing.com/problem/content/366/
  • 技巧:一开始的想法是ecc缩点之后,树链剖分回答询问,但是似乎有更好的做法,采用并查集暴力修改即可
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define sz(a) (int)a.size()
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)
#define sz(a) (int)a.size()
#define pii pair <int,int>
template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 2e5 + 5;
int n , m, Q;
vector <int> Edge[N];
int head[N] , cnt;
struct node {
    int t , next;
}edge[N << 1];
void add (int f, int t)
{
    edge[cnt].t = t;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
int bridge[N << 1], fa[N];
int vec[N << 1], deep[N << 1];
int dfn[N << 1] ,low[N << 1], times, ans;
void tarjan(int u, int id)
{
    dfn[u] = low[u] = ++times;
    for(int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if(!dfn[v])
        {
            deep[v] = deep[u] + 1;
            fa[v] = u;
            tarjan(v,i);
            low[u] = min (low[u] , low[v]);
            if(low[v] > dfn[u])
                bridge[i] = bridge[i^1] = 1, vec[v] = 1, ans += 1;
        }
        else if(i != (id ^ 1)) // 如果有重边
            low[u] = min(low[u], dfn[v]);
    }
}
//处理ecc
int ecc[N << 1], type, deg[N << 1];
void dfs(int u, int block)
{
    ecc[u] = block;
    for(int i = head[u] ; i != - 1 ; i = edge[i].next)
    {
        if(!bridge[i])
        {
            int v = edge[i].t;
            if(!ecc[v])
                dfs(v,block);
        }
    }
}
void Init ()
{
    for (int i = 1 ; i <= n ; i ++)
        Edge[i].clear();
    type = times = ans = cnt = 0;
    mem (fa, 0);
    mem (deep, 0);
    mem (head, -1);
    mem (ecc , 0);
    mem (deg , 0);
    mem (bridge, 0);
    mem (dfn, 0);
    mem (vec, 0);
}
void LCA (int u , int v)
{
    int ver[N] , tot = 0;
    if (deep[u] < deep[v])
        swap (u, v);
    while (deep[u] > deep[v])
    {
        if (vec[u])
            vec[u] = 0, ans --;
        ver[++ tot] = u;
        u = fa[u];
    }
    while (u != v)
    {
        if (vec[u]) ans -- , vec[u] = 0;
        if (vec[v]) ans -- , vec[v] = 0;
        ver[++ tot] = u;
        ver[++ tot] = v;
        u = fa[u];
        v = fa[v];
    }
    int lca = u;
    for (int i = 1 ; i <= tot ; i ++)
        fa[ver[i]] = lca;
    return ;
}
int main ()
{
    int t = 1;
    while (scanf("%d %d",&n , &m) != EOF)
    {
        if (n == 0 && m == 0)
            break;
        Init();
        for (int i = 1; i <= m; i++)
        {
            int u, v;
            read(u, v);
            add(u, v);
            add(v, u);
        }

        for (int i = 1; i <= n; i++)
            if (!dfn[i])
                tarjan(i, i);
        for (int i = 1; i <= n; i++)
            if (!ecc[i])
                dfs(i, ++type);

        for (int i = 0; i < cnt; i++)
        {
            int u = edge[i].t , v = edge[i + 1].t;
            if (ecc[u] != ecc[v])
                Edge[u].pb(v) , Edge[v].pb(u);
        }
        printf ("Case %d:\n", t ++);
        read (Q);
        for (int i = 1 ; i <= Q ; i ++)
        {
            int u , v;
            read (u , v);
            LCA (u , v);
            write (ans), LF;
        }
        LF;
    }
}

  • 题目连接:https://www.acwing.com/problem/content/397/
  • 技巧:ecc缩点之后,寻找度数为1的点的个数即可,其最终答案为**(叶子节点数+1)/2**
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define sz(a) (int)a.size()
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)
#define sz(a) (int)a.size()
#define pii pair <int,int>
template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 2e5 + 5;
int n , m, head[N] , cnt;
struct node {
    int t , next;
}edge[N << 1];
void add (int f, int t)
{
    edge[cnt].t = t;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
int bridge[N << 1];
int dfn[N << 1] , low[N << 1] , times;
void tarjan (int u , int id)
{
    dfn[u] = low[u] = ++times;
    for (int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if (!dfn[v])
        {
            tarjan (v, i);
            low[u] = min (low[u] , low[v]);
            if (low[v] > dfn[u])
                bridge[i] = bridge[i ^ 1] = 1;
        }
        else if (i != (id ^ 1))
            low[u] = min (low[u] , dfn[v]);
    }
}
int ecc[N << 1] , type, deg[N << 1];
void dfs (int u , int block)
{
    ecc[u] = block;
    for (int i = head[u] ; i != - 1; i = edge[i].next)
    {
        if (!bridge[i])
        {
            int v = edge[i].t;
            if (!ecc[v])
                dfs (v, block);
        }
    }
}
int main ()
{
    mem (head, -1);
    read (n , m);
    for (int i = 1 ; i <= m ; i ++)
    {
        int u , v;
        read (u, v);
        add (u, v);
        add (v, u);
    }
    for (int i = 1 ; i <= n ; i ++)
        if (!dfn[i])
            tarjan (i , i);

    for (int i = 1 ; i <= n ; i ++)
        if (!ecc[i])
            dfs (i , ++ type);

    for (int i = 0 ; i < cnt ; i += 2)
    {
        int u = edge[i].t , v = edge[i + 1].t;
        if (ecc[u] != ecc[v])
            deg[ecc[u]] ++ , deg[ecc[v]] ++;
    }
    int ans = 0;
    for (int i = 1 ; i <= type; i ++)
        if (deg[i] == 1)
            ans ++;
    write ((ans + 1) >> 1), LF;
}

  • 题目连接:https://www.acwing.com/problem/content/400/
  • 技巧:vcc缩点后,建立圆方树,在缩点的同时,处理边所属的点双,必经点的个数 == 两个方点距离/2
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define sz(a) (int)a.size()
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)
#define sz(a) (int)a.size()
#define pii pair <int,int>
template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 200005;
int n , m , head[N] , cnt;
struct node {
    int t, next;
}edge[N];
void add (int f, int t)
{
    edge[cnt].t = t;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
vector <pair<int,int>> Edge[N];
stack <int> st, st_e;
int dfn[N], low[N], fa[N], fa_id[N], times, vcc[N];
int vis[N], type;
void tarjan (int u)
{
    dfn[u] = low[u] = ++ times;
    st.push (u), vis[u] = 1;
    int v , id , x;
    for (auto it : Edge[u])
    {
        v = it.fi , id = it.se;
        if (!dfn[v])
        {
            fa[v] = u, fa_id[v] = id;
            st_e.push (id);
            tarjan (v);
            low[u] = min (low[u] , low[v]);
            if (dfn[u] <= low[v])
            {
                ++type;
                while (1)
                {
                    x = st.top();
                    add (type, x);
                    add (x, type);
                    vis[x] = 0 , st.pop();
                    if (x == v) break;
                }
                add (type, u);
                add (u, type);
                while (1)
                {
                    id = st_e.top() , st_e.pop();
                    vcc[id] = type;
                    if (id == fa_id[v]) break;
                }
            }
        }
        else if (v != fa[u])
        {
            low[u] = min (low[u], dfn[v]);
            if (dfn[v] < dfn[u]) st_e.push(id);
        }
    }
}
int bz[N << 1][30] , deep[N << 1];
void dfs(int rt, int dep)
{
    deep[rt] = dep;
    for (int i = head[rt] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if(!deep[v])
        {
            bz[v][0] = rt;
            dfs(v,dep + 1);
        }
    }
}
void deal()
{
    for (int i = 1 ; i <= 25 ; i ++)
        for (int j = 1 ; j <= 2 * n ; j ++)
            bz[j][i] = bz[bz[j][i-1]][i-1];
}
int LCA(int x,int y)
{
    if(deep[x] < deep[y])
        swap(x,y);
    int t = deep[x] - deep[y];
    for(int i = 0; (1<<i) <= t ; i ++)
        if((1<<i)&t)x=bz[x][i];
    for(int i=19 ; i >= 0 ; i--)
        if(bz[x][i] ^ bz[y][i])
            x=bz[x][i], y=bz[y][i];
    if(x==y) return x;
    return bz[x][0];
}
void Init()
{
    for (int i = 1 ; i <= n ; i ++)
        Edge[i].clear();
    cnt = 0;
    type= n;
    mem (vcc, 0);
    mem (head,-1);
    mem (dfn, 0);
    mem (deep, 0);
    mem (low, 0);
    mem (bz, 0);
    mem (fa, 0);
    mem (fa_id, 0);
}
int main ()
{
    while (scanf ("%d %d",&n, &m) != EOF)
    {
        if (n == 0 && m == 0)
            break;
        Init ();
        for (int i = 1 ; i <= m ; i ++)
        {
            int u , v;
            read (u, v);
            Edge[u].pb(mk(v, i));
            Edge[v].pb(mk(u, i));
        }
        for (int i = 1 ; i <= n ; i ++)
            if (!dfn[i])
                tarjan(i);
        dfs (1, 1);
        deal ();
        int Q;
        read (Q);
        while (Q --)
        {
            int u , v;
            read (u, v);
            u = vcc[u] , v = vcc[v];
            int lca = LCA (u, v);
            //cout << u << ' ' << v << ' ' << lca << endl;
            //cout << deep[u] << ' ' << deep[v] << ' ' << deep[lca] << endl;
            write ((deep[u] + deep[v] - 2 * deep[lca])/2), LF;
        }
    }
}

  • 题目连接:https://www.acwing.com/problem/content/description/365/
  • 技巧:建立圆方树,在圆方树上计数
//#define LOCAL
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define sz(a) (int)a.size()
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)
#define sz(a) (int)a.size()

template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 200005, M = 10000005;
int n , m;
vector <int> Edge[N];
stack <int> st;
int dfn[N] , low[N] , fa[N] , times, type;
struct node {
    int t, next;
}edge[M];
int head[N] , cnt;
void add (int f, int t)
{
    edge[cnt].t = t;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
void tarjan (int u)
{
    dfn[u] = low[u] = ++ times;
    st.push(u);
    for (auto v : Edge[u])
    {
        if (!dfn[v])
        {
            fa[v] = u;
            tarjan (v);
            low[u] = min (low[u], low[v]);
            if (dfn[u] <= low[v])
            {
                ++ type;
                while (1)
                {
                    int x = st.top();
                    add (type, x);
                    add (x, type);
                    st.pop();
                    if (x == v) break;
                }
                add (type, u);
                add (u, type);
            }
        }
        else if (v != fa[u])
            low[u] = min (low[u], dfn[v]);
    }

}
int siz[N];
ll ans[N];
void dfs (int u, int pa)
{
    siz[u] = (u <= n);
    int sum = 0;
    for (int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if (v != pa)
        {
            dfs (v, u);
            sum += siz[v];
            siz[u] += siz[v];
        }
    }
    int temp = n - siz[u];
    ans[u] = 2ll * (n - 1);
    for (int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if (v != pa)
        {
            ans[u] += 2ll * siz[v] * temp;
            temp += siz[v];
        }
    }
}
int main ()
{
    CLOSE;
    mem (head, -1);
    read (n, m);
    type = n;
    for (int i = 1 ; i <= m ; i ++)
    {
        int u , v;
        read (u, v);
        Edge[u].pb (v);
        Edge[v].pb (u);
    }
    for (int i = 1 ; i <= n ; i ++)
        if (!dfn[i])
        {
            tarjan (i);
            dfs (i, i);
        }
    for (int i = 1 ; i <= n ; i ++) cout << ans[i] << endl;
}
  • 题目链接: https://codeforces.com/problemset/problem/1389/G
  • e c c ecc ecc缩点之后形成一颗树,同时处理出所有节点的权值以及是否包含关键点。形成一颗树之后,选取一个包含关键点的节点作为根,然后将所有树的非关键节点合并到关键节点上,做树形DP

因为强制要求某个i点为饱和点,所有i子树的点要么无向要么必须指向i,i的父亲必须指向i,所以换根的时候只用考虑一条边贡献即可

第一次dp d p 1 [ u ] + = s u m [ u ] + m a x ( d p 1 [ v ] − c , 0 ) dp1[u] += sum[u] + max (dp1[v]-c,0) dp1[u]+=sum[u]+max(dp1[v]c,0)
第二次dp d p 2 [ u ] = m a x ( d p 1 [ u ] − m a x ( d p 1 [ v ] − c , 0 ) − c , 0 ) dp2[u] = max (dp1[u]-max(dp1[v]-c,0)-c, 0) dp2[u]=max(dp1[u]max(dp1[v]c,0)c,0)

输出所有dp2 即可

对于第二次换根DP , 由于dp1[v]表示子树V的所有关键节点能够到达V的最大值。 d p 1 [ u ] − m a x ( d p 1 [ v ] − c , 0 ) − c dp1[u]-max(dp1[v]-c,0)-c dp1[u]max(dp1[v]c,0)c的意义是 dp1[u]表示u的子树中所有关键节点到达u的最大贡献,减去v子树的贡献之后
那么剩余节点的贡献就是 d p 1 [ u ] − m a x ( d p 1 [ v ] − c , 0 ) dp1[u]-max(dp1[v]-c,0) dp1[u]max(dp1[v]c,0)
1.然后如果定向u-v这条边,除V子树外的所有节点都不可能为饱和节点。因为反向了,V的所有节点是指向V的。而定向的意义是 u − v u-v uv
2.如果不定向 u − v u-v uv这条边,那么和dp1的讨论方式是一样的.

const int N = 3e5 + 5;
int n , m , k;
int a[N], c[N], w[N];
int head[N], cnt;
struct node {
    int t , next , w;
}edge[N << 1];
void add (int f, int t, int w)
{
    edge[cnt].t = t;
    edge[cnt].w = w;
    edge[cnt].next = head[f];
    head[f] = cnt ++;
}
int bridge[N << 1];
int dfn[N << 1] ,low[N << 1], times;
void tarjan(int u, int id)
{
    dfn[u] = low[u] = ++times;
    for(int i = head[u] ; i != -1 ; i = edge[i].next)
    {
        int v = edge[i].t;
        if(!dfn[v])
        {
            tarjan(v,i);
            low[u] = min (low[u] , low[v]);
            if(low[v] > dfn[u])
                bridge[i] = bridge[i^1] = 1;
        }
        else if(i != (id ^ 1)) // 如果有重边
            low[u] = min(low[u], dfn[v]);
    }
}
int ecc[N << 1], type;
ll sum[N];
int rt, fa[N], suma[N];
void dfs(int u, int block)
{
    suma[block] |= a[u];
    sum[block] += c[u];
    ecc[u] = block;
    for(int i = head[u] ; i != - 1 ; i = edge[i].next)
    {
        if(!bridge[i])
        {
            int v = edge[i].t;
            if(!ecc[v])
                dfs(v,block);
        }
    }
    if (suma[block])
        rt = block;
}
vector <pii> Edge[N];
void dfs1 (int u , int pa)
{
    for (auto it : Edge[u])
    {
        int v = it.fi;
        if (v != pa)
        {
            dfs1 (v, u);
            suma[u] |= suma[v];
            if (suma[v] == 0)
                sum[u] += sum[v], fa[v] = u;
        }
    }
}
ll dp1[N] , dp2[N];
void dfs2 (int u , int pa)
{
    for (auto it : Edge[u])
    {
        int v = it.fi , cc = it.se;
        if (v != pa && suma[v])
        {
            dfs2 (v, u);
            dp1[u] += max (dp1[v]-cc,0ll);
        }
    }
    dp1[u] += sum[u];
}
void dfs3 (int u ,int pa)
{
    dp2[u] += dp1[u];
    for (auto it : Edge[u])
    {
        int v = it.fi , cc = it.se;
        if (v != pa && suma[v])
        {
            dp2[v] += max (dp2[u]-max(dp1[v]-cc,0ll)-cc, 0ll);
            dfs3 (v, u);
        }
    }
}
int find (int x)
{
    if(x!=fa[x]) fa[x]=find(fa[x]);
    return fa[x];
}
int main ()
{
    CLOSE;
    mem (head, -1);
    cin >> n >> m >> k;
    for (int i = 1 ; i <= k ; i ++)
    {
        int x;
        cin >> x;
        a[x] = 1;
    }
    for (int i = 1 ; i <= n ; i ++)
        cin >> c[i], fa[i] = i;
    for (int i = 1 ; i <= m ; i ++)
        cin >> w[i];
    for (int i = 1 ; i <= m ; i ++)
    {
        int u , v;
        cin >> u >> v;
        add (u, v , w[i]);
        add (v, u , w[i]);
    }
    for (int i = 1 ; i <= n ; i ++)
        if (!dfn[i])
            tarjan (i, -1);
    for (int i = 1 ; i <= n ; i ++)
        if (!ecc[i])
            dfs (i, ++ type);

    for (int i = 0 ; i < cnt ; i += 2)
    {
        int u = edge[i].t, v = edge[i+1].t, ww = edge[i].w;
        if (ecc[u] != ecc[v])
        {
            Edge[ecc[u]].pb(mk(ecc[v], ww));
            Edge[ecc[v]].pb(mk(ecc[u],ww));
        }
    }
    dfs1 (rt, 0);
    dfs2 (rt, 0);
    dfs3 (rt, 0);
    for (int i = 1 ; i <= n ; i ++)
        cout << dp2[find(ecc[i])] << ' ';
    cout << endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值