缩点

独自空忆成欢 提交于 2019-12-03 06:31:38

老师讲图论-缩点-复习

我想 我没学过 缩点啊 \(OTL\)

先讲 割点

定义 在无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点

P3388 【模板】割点(割顶)

主要思想

观察\(DFS\)搜索树,我们可以发现有两类节点可以成为割点:

  • 对根节点\(u\),若其有两棵或两棵以上的子树,则该根结点u为割点;

  • 对非叶子节点\(u\)(非根节点),若其中的某棵子树的节点均没有指向u的祖先节点的回边,说明删除u之后,根结点与该棵子树的节点不再连通;则节点u为割点。

我们用\(dfn[u]\)记录节点u在DFS过程中被遍历到的次序号,\(low[u]\)记录节点\(u\)\(u\)的子树通过非父子边追溯到最早的祖先节点(即\(DFS\)次序号最小),那么\(low[u]\)的计算过程如下:

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define reg register int
#define isdigit(x) ('0' <= (x)&&(x) <= '9')
template<typename T>
inline T Read(T Type)
{
    T x = 0,f = 1;
    char a = getchar();
    while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();}
    while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
    return x * f;
}
const int MAXN = 20010,MAXM = 200010;
bool vis[MAXN],out[MAXN];
int head[MAXN],cnt,dfn[MAXN],low[MAXN],fa[MAXN],tot,root;
struct node
{
    int v,next;
}edge[MAXM];
inline void addedge(int u,int v)
{
    edge[++cnt].v = v;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
inline void Tarjan(int x)
{
    vis[x] = 1,dfn[x] = low[x] = ++tot;
    int kid = 0;
    for(reg i = head[x];i;i = edge[i].next)
    {
        int v = edge[i].v;
        if(!vis[v])
        {
            Tarjan(v);
            if(x == root)kid++;
            fa[v] = x;
            low[x] = min(low[x],low[v]);
            if(x != root&&low[v] >= dfn[x]) out[x] = 1;
        }
        low[x] = min(low[x],dfn[v]); 
    }
    if(x == root&&kid >= 2)
        out[x] = 1;
}
int main()
{
    int n = Read(1),m = Read(1);
    for(reg i = 1;i <= m;i++)
    {
        int u = Read(1),v = Read(1);
        addedge(u,v),addedge(v,u);
    }
    for(reg i = 1;i <= n;i++)
        if(!vis[i])
        {
            root = i;
            Tarjan(i);
        }
    int size = 0;
    for(reg i = 1;i <= n;i++) if(out[i]) size++;
    printf("%d\n",size);
    for(reg i = 1;i <= n;i++)
        if(out[i]) printf("%d ",i);
    return 0;
}

缩点

就将强连通分量的点 缩成一个点

P3387 【模板】缩点

补充:\(DAG\) 有向无环图

\(Tarjan\)缩点\(+DAGdp\)

\(DAGdp\)\(dp\)

如果是这样的\(dp[son]=f(dp[fa])\)
先要拓扑排序

#include <stack>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define reg register int
#define isdigit(x) ('0' <= x&&x <= '9') 
template<typename T>
inline T Read(T Type)
{
    T x = 0,f = 1;
    char a = getchar();
    while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();}
    while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
    return x * f;
}
const int MAXN = 1e4 + 10,MAXM = 1e5 + 10;
bool vis[MAXN],instack[MAXN];
stack<int> st;
vector<int> seq,ahead[MAXN],behind[MAXN];
int n,cnt,d,cnt_scc,ans,dp[MAXN],head[MAXN],tot;
struct point
{
    int scc,dfn,low,power;
}poi[MAXN];
struct SCC
{
    int power,In;
}scc[MAXN];
struct node
{
    int u,v,next;
}edge[MAXM];
inline void addedge(int u,int v)
{
    edge[++cnt].v = v;
    edge[cnt].u = u;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
inline void tarjan(int x)
{
    poi[x].dfn = poi[x].low = ++tot;
    vis[x] = 1,st.push(x),instack[x] = 1;
    for(reg i = head[x];i;i = edge[i].next)
    {
        int v = edge[i].v;
        if(!vis[v])
        {
            tarjan(v);
            poi[x].low = min(poi[x].low,poi[v].low);
        } else if(instack[v]) poi[x].low = min(poi[x].low,poi[v].dfn);
    }
    if(poi[x].dfn == poi[x].low)
    {
        scc[++cnt_scc].power = scc[++cnt_scc].In = 0;
        while(1)
        {
            int t = st.top();st.pop();
            poi[t].scc = cnt_scc;instack[t] = 0;
            scc[cnt_scc].power += poi[t].power;
            if(t == x) break;
        }
    }
    return;
}
inline void topo()
{
    queue<int> q;
    for(reg i = 1;i <= cnt_scc;i++)
        if(!scc[i].In) q.push(i);
    while(!q.empty())
    {
        int it = q.front();q.pop();
        seq.push_back(it);
        for(reg i = 0;i < behind[it].size();i++)
        {
            int v = behind[it][i];
            scc[v].In--;
            if(!scc[v].In) q.push(v);
        }
    }
}
int main()
{
    n = Read(1);int m = Read(1);
    for(reg i = 1;i <= n;i++) poi[i].power = Read(1);
    for(reg i = 1;i <= m;i++)
    {
        int u = Read(1),v = Read(1);
        addedge(u,v);
    }
    for(reg i = 1;i <= n;i++)
        if(!poi[i].dfn) tarjan(i);
    for(reg i = 1;i <= m;i++)
    {
        int u = edge[i].u,v = edge[i].v;
        if(poi[u].scc != poi[v].scc)
        {
            int sccu = poi[u].scc,sccv = poi[v].scc;
            scc[sccv].In++,behind[sccu].push_back(sccv),ahead[sccv].push_back(sccu);
        }   
    }
    topo();
    for(reg i = 0;i < seq.size();i++)
    {
        int x = seq[i];
        dp[x] = scc[x].power;
        for(reg j = 0;j < ahead[x].size();j++)
            dp[x] = max(dp[ahead[x][j]] + scc[x].power,dp[x]);
        ans = max(ans,dp[x]);
    }
    printf("%d\n",ans);
    return 0;
}

P2002 消息扩散

\(Tarjan\)缩点后 求入度为零的点の数

#include <stack>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define reg register int
#define isdigit(x) ('0' <= x&&x <= '9') 
template<typename T>
inline T Read(T Type)
{
    T x = 0,f = 1;
    char a = getchar();
    while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();}
    while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
    return x * f;
}
const int MAXN = 1e5 + 10,MAXM = 5e5 + 10;
bool vis[MAXN],instack[MAXN];
stack<int> st;
vector<int> seq,ahead[MAXN],behind[MAXN];
int n,cnt,d,cnt_scc,ans,dp[MAXN],head[MAXN],tot;
struct point
{
    int scc,dfn,low;
}poi[MAXN];
struct SCC
{
    int In;
}scc[MAXN];
struct node
{
    int u,v,next;
}edge[MAXM];
inline void addedge(int u,int v)
{
    edge[++cnt].v = v;
    edge[cnt].u = u;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
inline void tarjan(int x)
{
    poi[x].dfn = poi[x].low = ++tot;
    vis[x] = 1,st.push(x),instack[x] = 1;
    for(reg i = head[x];i;i = edge[i].next)
    {
        int v = edge[i].v;
        if(!vis[v])
        {
            tarjan(v);
            poi[x].low = min(poi[x].low,poi[v].low);
        } else if(instack[v]) poi[x].low = min(poi[x].low,poi[v].dfn);
    }
    if(poi[x].dfn == poi[x].low)
    {
        scc[++cnt_scc].In = 0;
        while(1)
        {
            int t = st.top();st.pop();
            poi[t].scc = cnt_scc;instack[t] = 0;
            if(t == x) break;
        }
    }
    return;
}
int main()
{
    n = Read(1);int m = Read(1);
    for(reg i = 1;i <= m;i++)
    {
        int u = Read(1),v = Read(1);
        addedge(u,v);
    }
    for(reg i = 1;i <= n;i++)
        if(!poi[i].dfn) tarjan(i);
    for(reg i = 1;i <= m;i++)
    {
        int u = edge[i].u,v = edge[i].v;
        if(poi[u].scc != poi[v].scc)
        {
            int sccu = poi[u].scc,sccv = poi[v].scc;
            scc[sccv].In++,behind[sccu].push_back(sccv),ahead[sccv].push_back(sccu);
        }   
    }
    for(reg i = 1;i <= cnt_scc;i++)
        ans += !scc[i].In;
    printf("%d\n",ans);
    return 0;
}

P1262 间谍网络

先判是否有解 \(DFS\)

\(Tarjan\)缩点后,求入读为零的点的权值之和最小(缩点后的点的权值保持前面的点权最小值)

#include <stack>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define reg register int
#define isdigit(x) ('0' <= x&&x <= '9') 
template<typename T>
inline T Read(T Type)
{
    T x = 0,f = 1;
    char a = getchar();
    while(!isdigit(a)) {if(a == '-') f = -1;a = getchar();}
    while(isdigit(a)) {x = (x << 1) + (x << 3) + (a ^ '0');a = getchar();}
    return x * f;
}
const int MAXN = 1e5 + 10,MAXM = 5e5 + 10;
bool vis[MAXN],esp[MAXN],instack[MAXN];
stack<int> st;
int area,n,cnt,cnt_scc,ans,head[MAXN],tot;
struct point {int scc,dfn,low,cost;} poi[MAXN];
struct SCC {int In,cost;} scc[MAXN];
struct node {int u,v,next;} edge[MAXM];
inline void addedge(int u,int v)
{
    edge[++cnt].v = v;
    edge[cnt].u = u;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
inline void dfs(int x)
{   
    vis[x] = 1;
    ++area;
    for(reg i = head[x];i;i = edge[i].next)
    {
        int v = edge[i].v;
        if(!vis[v]) dfs(v);
    }
    return;
}
inline void tarjan(int x)
{
    poi[x].dfn = poi[x].low = ++tot;
    vis[x] = 1,st.push(x),instack[x] = 1;
    for(reg i = head[x];i;i = edge[i].next)
    {
        int v = edge[i].v;
        if(!vis[v])
        {
            tarjan(v);
            poi[x].low = min(poi[x].low,poi[v].low);
        } else if(instack[v]) poi[x].low = min(poi[x].low,poi[v].dfn);
    }
    if(poi[x].dfn == poi[x].low)
    {
        ++cnt_scc;
        while(1)
        {
            int t = st.top();st.pop();
            scc[cnt_scc].cost = min(scc[cnt_scc].cost,poi[t].cost);
            poi[t].scc = cnt_scc,instack[t] = 0;
            if(t == x) break;
        }
    }
    return;
}
int main()
{
    memset(scc,0,sizeof(scc));
    n = Read(1);
    int p = Read(1),m;
    for(reg i = 1;i <= n;i++) scc[i].cost = poi[i].cost = MAXN;
    for(reg i = 1;i <= p;i++)
    {
        int k;
        poi[k = Read(1)].cost = Read(1);
        esp[k] = 1;
    }
    m = Read(1);
    for(reg i = 1;i <= m;i++)
    {
        int u = Read(1),v = Read(1);
        if(u == v) continue;
        addedge(u,v);
    }
    for(reg i = 1;i <= n;i++) if(esp[i]) dfs(i);
    if(area < n)
    {
        printf("NO\n");
        for(reg i = 1;i <= n;i++)
            if(!vis[i])
            {
                printf("%d",i);
                return 0;
            }
    }
    memset(vis,0,sizeof(vis));
    for(reg i = 1;i <= n;i++)
        if(!poi[i].dfn) tarjan(i); 
    for(reg i = 1;i <= m;i++)
    {
        int u = edge[i].u,v = edge[i].v;
        if(poi[u].scc != poi[v].scc)
        {
            int sccu = poi[u].scc,sccv = poi[v].scc;
            scc[sccv].In++;
        }   
    }
    for(reg i = 1;i <= cnt_scc;i++)
        if(!scc[i].In)
            ans += scc[i].cost;
    printf("YES\n%d\n",ans);
    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!