洛谷 P1262|P2341|P2002 强连通分量,缩点

图论强连通分量算法,个人感觉tarjan相比两次dfs好写一点(个人看法)

这三道题都在学了强连通分量算法之后都比较基础,貌似都要判断一下缩点之后每个点的入度?
P1262 间谍网络
题意:
直接复制一下数据的输入格式这里,还是比较好理解的吧
第一行只有一个整数n。
第二行是整数p。表示愿意被收买的人数,1≤p≤n。
接下来的p行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他将会被收买的数额。这个数额不超过20000。
紧跟着一行只有一个整数r,1≤r≤8000。然后r行,每行两个正整数,表示数对(A, B),A间谍掌握B间谍的证据。

那么对于数对(A,B),我们可以使A指向B建图,之后tarjan算法求强连通分量缩点
tarjan算法退栈的时候,需要判断一下每一个点内部是否都有可以收买的间谍,并且求出内部的点最小的编号
最后处理一下所有的点的入度,检查入度为0的点,如果有一个点的内部没有可以收买的间谍的话直接输出它最小的编号就可以了
代码:

//Decision's template
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#include<string>
#include<cmath>
#include<map>
#include<set>
using namespace std;

#define DP_maxn 16
#define maxn 100000
#define INF 10000007
#define mod 1000000007
#define mst(s,k) memset(s,k,sizeof(s))

typedef long long ll;

struct Edge{
    int from,to,dist;
    Edge(int u,int v,int d):from(u),to(v),dist(d){}
};

/*-------------------------------template End--------------------------------*/

int n,need[maxn],p,r,ans = 0,minid[maxn];
int First[maxn],Next[maxn],Last[maxn],a[maxn],k = 0;
int dfn[maxn],low[maxn],top = 0,q[maxn],fa[maxn],sum = 0,minn[maxn],cnt = 0,s[maxn],in[maxn];
bool instack[maxn],flag[maxn];

void init()
{
    mst(First,0);
    mst(low,0);
    mst(dfn,0);
    mst(s,0);
    mst(in,0);
    for(int i = 0;i<=3000+10;i++) need[i] = INF,minid[i] = INF,minn[i] = 0;
}

void add(int x,int y)
{
    k++;
    a[k] = y;
    if(First[x]==0) First[x] = k;
    else Next[Last[x]] = k;
    Last[x] = k;
}

void tarjan(int x)
{
    cnt++;top++;
    low[x] = dfn[x] = cnt;
    q[top] = x;
    instack[x] = true;
    int t = First[x];
    while(t!=0)
    {
        int v = a[t];
        if(dfn[v]==0)
        {
            tarjan(v);
            low[x] = min(low[x],low[v]);
        }
        else if(instack[v]) low[x] = min(low[x],dfn[v]);
        t = Next[t];
    }
    if(dfn[x]==low[x])
    {
        sum++;
        while(q[top+1] != x)
        {
            int tt = q[top];
            minid[sum] =  min(minid[sum],tt);
            //cout<<need[tt]<<" "<<need[minn[sum]]<<" "<<need[tt]<<endl;
            if(need[tt]!=0&&need[minn[sum]]>need[tt]) {minn[sum] = tt;flag[sum] = 1;}
            //cout<<minn[sum]<<endl;
            s[sum]++;
            fa[tt] = sum;
            instack[tt] = false;
            top--;
        }
    }
    //cout<<minn[sum]<<endl;
}

int main()
{
    //freopen("std.in","r",stdin);
    //freopen("std.out","w",stdout);
    init();
    cin>>n>>p;
    for(int i = 1;i<=p;i++)
    {
        int id,money;
        cin>>id>>money;
        need[id] = money;
    }
    cin>>r;
    for(int i = 1;i<=r;i++)
    {
        int tmpx,tmpy;
        cin>>tmpx>>tmpy;
        add(tmpx,tmpy);
    }
    for(int i = 1;i<=n;i++)
    {
        if(dfn[i]==0) tarjan(i);
    }
    for(int i = 1;i<=n;i++)
    {
        for(int j = First[i];j;j = Next[j])
        {
            int y = a[j];
            if(fa[i]!=fa[y]) in[fa[y]]++; 
        }    
    }
    for(int i = 1;i<=sum;i++)
    {
        if(in[i]==0)
        {
            if(flag[i]==0) {cout<<"NO"<<endl<<minid[i]<<endl;return 0;}
            else ans+=need[minn[i]];
        }
    }
    cout<<"YES"<<endl<<ans<<endl;
    return 0;
}

P2341 [HAOI]受欢迎的牛
N个点M条有向边,给出一点的关系构建一个有向图
求强连通分量,因为在一个强连通分量内部,一个点可以到达其他的所有点,那么也就是说在里面一头牛会认为其他的牛都是受欢迎的,那么可以缩点
缩点之后,遍历一下所有的点,求出缩点之后各点的入度,出度为0的点为所求答案,ans++
代码:
//Decision's template
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#include<string>
#include<cmath>
#include<map>
#include<set>
using namespace std;

#define DP_maxn 16
#define maxn 100000
#define INF 10000007
#define mod 1000000007
#define mst(s,k) memset(s,k,sizeof(s))

typedef long long ll;

struct Edge{
    int from,to,dist;
    Edge(int u,int v,int d):from(u),to(v),dist(d){}
};

/*-------------------------------template End--------------------------------*/

int n,m,tmpx,tmpy,top = 0,k = 0,cnt = 0;
int First[maxn],Next[maxn],Last[maxn],a[maxn*2],pe[maxn],ppe[maxn];
int dfn[maxn],tmp = 0,low[maxn],q[maxn],fa[maxn];
bool instack[maxn];

void init(){
    mst(First,0);
    mst(Next,0);
    mst(a,0);
    mst(dfn,0);
    mst(low,0);
}

void add(int x,int y)
{
    k++,a[k] = y;
    if(First[x]==0) First[x] = k;
    else Next[Last[x]] = k;
    Last[x] = k;
}

void tarjan(int x)
{
    top++;cnt++;
    dfn[x] = low[x] = cnt;
    q[top] = x;
    instack[x] = true;
    int t = First[x];
    while(t!=0){
        int v = a[t];
        if(dfn[v] == 0){
            tarjan(v);
            if(low[v]<low[x]) low[x] = low[v];
        }
        else if(instack[v]&&dfn[v] < low[x]) low[x] = dfn[v];
        t= Next[t];
    }
    if(dfn[x] == low[x])
    {
        n++;
        while(q[top+1]!=x)
        {
            pe[n]++;
            fa[q[top]] = n;
            instack[q[top]] = false;
            top--;
        }
    }
}

int main()
{
    //freopen("std.in","r",stdin);
    //freopen("std.out","w",stdout);
    init();
    cin>>n>>m;
    for(int i = 1;i<=m;i++)
    {
        cin>>tmpx>>tmpy;
        add(tmpx,tmpy);
    }
    m = n+1;
    for(int i = 1;i<=m-1;i++)
        if(dfn[i]==0) tarjan(i);
    for(int i = 1;i<=m-1;i++){
        for(int o = First[i];o;o = Next[o])
        if(fa[i]!=fa[a[o]]) ppe[fa[i]]++;
    }
    int pp = 0;
    for(int i = m;i<=n;i++) if(ppe[i] ==0) pp++;
    if(pp==1) cout<<pe[m]<<endl;
    else cout<<"0"<<endl;
    return 0;
}
P2002 消息扩散
和上面一题差不多,就是在入度为0的点发布就可以让全部城市都得到消息
//Decision's template
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#include<string>
#include<cmath>
#include<map>
#include<set>
using namespace std;

#define DP_maxn 16
#define maxn 100000*6
#define INF 10000007
#define mod 1000000007
#define mst(s,k) memset(s,k,sizeof(s))

typedef long long ll;

struct Edge{
    int from,to,dist;
    Edge(int u,int v,int d):from(u),to(v),dist(d){}
};

/*-------------------------------template End--------------------------------*/

int n,m,tmpx,tmpy,ans = 0;
int First[maxn],Next[maxn],Last[maxn],k = 0,ru[maxn];
int q[maxn],top = 0,a[maxn],dfn[maxn],low[maxn],cnt = 0,fa[maxn],num[maxn],sum = 0;
bool instack[maxn];

void add(int x,int y)
{
    k++;
    a[k] = y;
    if(First[x]==0) First[x] = k;
    else Next[Last[x]] = k;
    Last[x] = k;
}

void tarjan(int x)
{
    top++;cnt++;
    dfn[x] = low[x] = cnt;
    q[top] = x;
    instack[x] = true;
    int t = First[x];
    while(t!=0)
    {
        int v = a[t];
        if(dfn[v]==0){
            tarjan(v);
            if(low[v]<low[x]) low[x] = low[v];
        }
        else if(instack[v]&&dfn[v]<low[x]) low[x] = dfn[v];
        t = Next[t];
    }
    if(dfn[x]==low[x])
    {
        sum++;
        while(q[top+1]!=x)
        {
            num[sum]++;
            fa[q[top]] = sum;
            instack[q[top]] = false;
            top--;
        }
    }
}

void init()
{
    mst(dfn,0);
    mst(low,0);
    mst(num,0);
    mst(fa,0);
}

int main()
{
    //freopen("std.in","r",stdin);
    //freopen("std.out","w",stdout);
    init();
    cin>>n>>m;
    for(int i = 1;i<=m;i++){
        cin>>tmpx>>tmpy;
        add(tmpx,tmpy);
    }
    for(int i = 1;i<=n;i++)
    {
        if(dfn[i]==0) tarjan(i);
    }
    for(int i = 1;i<=n;i++)
    {
        for(int j = First[i];j;j = Next[j])
        {
            int v = a[j];
            if(fa[i]!=fa[v]) ru[fa[v]]++;
        }
    }
    for(int i = 1;i<=sum;i++)
    {
        if(ru[i]==0) ans++;
    }
    cout<<ans<<endl;
    return 0;
}


<think>好的,用户想了解双连通分量的概念、求法,以及P8435的C++代码。首先,我需要回顾双连通分量的定义。双连通分量是无向图中的极大子图,满足删除任意一个顶后子图仍然连通。这意味着图中没有割,或者说任意两间存在至少两条不重复的路径。 接下来,关于求法,通常使用Tarjan算法。这里需要注意,双连通分量的Tarjan算法和边双连通分量或强连通分量有所不同。需要记录dfn和low数组,同时利用栈来保存当前路径上的边。当发现某个顶u的子节v无法通过回边到达u的祖先时,u就是割,此时需要将栈中的边弹出,直到当前边,这些边属于同一个双连通分量。 然后,用户需要C++实现的P8435模板题代码。我需要参考已有的Tarjan算法实现,特别是处理双的部分。根据引用中的代码,比如引用3中的强连通分量模板,但双的处理方式不同,需要使用顶栈,并且在发现割时提取分量。需要注意的是,每个割可能属于多个双,而非割则只属于一个双。 代码结构方面,需要构建无向图,使用dfn和low数组,栈来保存节,当检测到low[v] >= dfn[u]时,说明u是割,开始弹出节直到v。同时,根节需要特殊处理,如果根的子节数大于1,则根也是割。 在编写代码时,要注意无向图的边处理,避免重复访问。通常每条边存储两次,所以遍历时需记录父节来防止回溯。此外,每个双连通分量至少包含两个顶或一个边(如两个顶一条边的情况)。 最后,根据P8435的要求,输出每个双连通分量的成员。需要测试代码的正确性,确保所有情况覆盖,比如孤立、自环等特殊情况可能不需要考虑,但按题目输入处理即可。 总结步骤:解释双概念,Tarjan算法的步骤,给出C++代码,并确保代码正确处理割和分量的提取,同时符合题目要求的输出格式。</think>### 双连通分量概念 **双连通分量**(Biconnected Component)是无向图中的极大子图,满足删除任意一个顶后子图仍然保持连通。即:任意两间存在至少两条不重复的路径[^1]。例如,环结构是典型的双连通分量,而连接两个环的公共顶(即割)会将图分为多个双连通分量。 ### 求法:Tarjan算法 1. **割判定**:顶$u$是割当且仅当满足: - 若$u$是根节,且子树数量$≥2$ - 若$u$非根,且存在子节$v$满足$low[v] \geq dfn[u]$ 2. **算法流程**: - 维护栈记录遍历路径 - 发现割时,从栈中弹出节构成一个双连通分量 - 割属于多个双,需保留在栈中 --- ### C++实现(P8435模板题) ```cpp #include <bits/stdc++.h> using namespace std; const int N = 5e5 + 10; vector<int> g[N], ans[N]; int dfn[N], low[N], stk[N], idx, top, cnt; int n, m; void tarjan(int u, int fa) { dfn[u] = low[u] = ++idx; stk[++top] = u; int child = 0; for (int v : g[u]) { if (!dfn[v]) { child++; tarjan(v, u); low[u] = min(low[u], low[v]); if (low[v] >= dfn[u]) { // 割判定 cnt++; int x; do { x = stk[top--]; ans[cnt].push_back(x); } while (x != v); // 弹出至v(不弹出u) ans[cnt].push_back(u); // 将割u加入当前双 } } else if (v != fa) { low[u] = min(low[u], dfn[v]); } } if (fa == 0 && child == 0) // 孤立单独成块 ans[++cnt].push_back(u); } int main() { cin >> n >> m; for (int i = 0; i < m; i++) { int u, v; scanf("%d%d", &u, &v); if (u == v) continue; // 自环无需处理 g[u].push_back(v); g[v].push_back(u); } for (int i = 1; i <= n; i++) if (!dfn[i]) { top = 0; tarjan(i, 0); } cout << cnt << endl; for (int i = 1; i <= cnt; i++) { printf("%d ", ans[i].size()); for (int x : ans[i]) printf("%d ", x); puts(""); } return 0; } ``` #### 代码说明 1. **输入处理**:忽略自环边 2. **Tarjan核心**: - 使用栈记录遍历路径 - 当发现割时,弹出子节部分构成双,保留割 3. **孤立处理**:单独作为双输出 4. **复杂度**:$O(n + m)$ ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值