ACM连通图问题总结

连通图一般来说是比较简单的,但是其变形非常的多,参透其思想是很重要的。关于连通图,比较经典的挑战和紫书上的讲解就已经足够。

POJ - 1236 强连通分量缩点

这道题的题意是有N个学校,每个学校到其他的学校有一条单向的传输路径。题目要求先求出强连通分量的个数。还有一问是至少要添加多少单向边,可以让整个图变成强连通图。

首先我们能想到利用强连通分量来缩点。现在我们假设已经求出了强连通分量,那么第一问就结束了,首先来考虑一下Scc == 1的时候,此时所有的点都可以互相到达,那么就不需要多添加变了。之后我们来考虑一下缩点,我们把每一个连通分量看成一个点,那么这些点肯定无法形成强连通图,我们要让这个突变成强连通图,也就说从任何一点出发,都可以到达其他的点,那么我们来算出所点之后的图出度为0和入度为0的点有多少个,取最大值就行。也就相当于是出度为零的点向入读为0的点连一条边。

#include <cstdio>
#include <cstring>
#include <iostream>
#define ll long long
using namespace std;
const int MAXN = 20010;//点数
const int MAXM = 50010;//边数
struct Edge
{
    int to,next;
} edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~scc
int Index,top;
int in[MAXN],out[MAXN];
int n;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
//num数组不一定需要,结合实际情况
void addedge(int u,int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
void Tarjan(int u)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if( !DFN[v] )
        {
            Tarjan(v);
            if( Low[u] > Low[v] )
                Low[u] = Low[v];
        }
        else if(Instack[v] && Low[u] > DFN[v])
            Low[u] = DFN[v];
    }
    if(Low[u] == DFN[u])
    {
        scc++;
        do
        {
            v = Stack[--top];
            Instack[v] = false;
            Belong[v] = scc;
            num[scc]++;
        }
        while( v != u);
    }
}
void solve(int N)
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    memset(num,0,sizeof(num));
    Index = scc = top = 0;
    for(int i = 1; i <= N; i++)
        if(!DFN[i])
            Tarjan(i);
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    for(int i = 1;i<=N;i++)
    {
        for(int j = head[i];j != -1 ;j = edge[j].next)
        {
            int v = edge[j].to;
            if(Belong[v] != Belong[i])
            {
                out[Belong[i]] ++;
                in[Belong[v]] ++;
            }
        }
    }
    int t1 = 0,t2 = 0;
    for(int i = 1;i<=scc;i++)
    {
        if(in[i] == 0) t1++;
        if(out[i] == 0) t2++;
    }
    if(scc == 1)
    {
        printf("1\n0\n");
    }
    else
    {
        printf("%d\n%d\n",t1,max(t1,t2));
    }
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
int main()
{

    while(scanf("%d", &n) != EOF)
    {
        init();
        for(int i = 1; i<=n; i++)
        {
            int x;
            while(1)
            {
                scanf("%d",&x);
                if(x == 0)
                    break;
                addedge(i,x);
            }
        }
        solve(n);

    }
    return 0;
}

这道题不仅仅需要求出来强连通分量,还需要之前求出来的进行缩点,缩点也是图论问题中经常碰到的问题。

找桥 UVA - 796

求桥的裸题,记得要排序输出。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10010;
const int MAXM = 100010;
struct Edge
{
    int to,next;
    bool cut;//是否为桥的标记
} edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN];
int Index,top;
bool Instack[MAXN];
bool cut[MAXN];
int add_block[MAXN];//删除一个点后增加的连通块
int bridge;
void addedge(int u,int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    edge[tot].cut = false;
    head[u] = tot++;
}
void Tarjan(int u,int pre)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    int son = 0;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if(v == pre)
            continue;
        if( !DFN[v] )
        {
            son++;
            Tarjan(v,u);
            if(Low[u] > Low[v])
                Low[u] = Low[v];
//桥
//一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。
            if(Low[v] > DFN[u])
            {
                bridge++;
                edge[i].cut = true;
                edge[i^1].cut = true;
            }
//割点
//一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。
//(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,
//即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)
            if(u != pre && Low[v] >= DFN[u])//不是树根
            {
                cut[u] = true;
                add_block[u]++;
            }
        }
        else if( Low[u] > DFN[v])
            Low[u] = DFN[v];
    }
//树根,分支数大于1
    if(u == pre && son > 1)
        cut[u] = true;
    if(u == pre)
        add_block[u] = son - 1;
    Instack[u] = false;
    top--;
}
void solve(int N)
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    memset(add_block,0,sizeof(add_block));
    memset(cut,false,sizeof(cut));
    Index = top = 0;
    bridge = 0;
    for(int i = 1; i <= N; i++)
        if( !DFN[i] )
            Tarjan(i,i);
    printf("%d critical links\n",bridge);
    vector<pair<int,int> >ans;
    for(int u = 1; u <= N; u++)
        for(int i = head[u]; i != -1; i = edge[i].next)
            if(edge[i].cut && edge[i].to > u)
            {
                ans.push_back(make_pair(u,edge[i].to));
            }
    sort(ans.begin(),ans.end());
//按顺序输出桥
    for(int i = 0; i < ans.size(); i++)
        printf("%d - %d\n",ans[i].first-1,ans[i].second-1);
    printf("\n");
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
int main()
{
    int n,u,v,k;
    while(~scanf("%d",&n))
    {
        init();
        for(int i = 1;i<=n;i++)
        {
            scanf("%d (%d)",&u,&k);
            u++;
            while(k--)
            {
                scanf("%d",&v);
                v++;
                addedge(u,v);
                addedge(v,u);
            }
        }
        solve(n);
    }

    return 0;
}

POJ - 3694 Tarjan + LCA

这是一道非常好的题,题意是有q个询问,每次询问添加一条边,问当前询问下还有多少个桥。

看到询问我们就能想到LCA,我们首先可以想到找桥的时候将非桥边的点加入并查集,剩下桥边。然后我们利用DFN暴力找LCA,然后在找的过程中,如果碰到一个点和自己的父节点中间的边是桥边的情况,这个时候桥数减一。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int MAXN = 100010;
const int MAXM = 400010;
struct Edge
{
    int to,next;
    bool cut;//是否为桥的标记
} edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN];
int Index,top;
bool Instack[MAXN];
int f[MAXN];
int fa[MAXN];
bool cut[MAXN];
int add_block[MAXN];//删除一个点后增加的连通块
int bridge;
int Find(int u)
{
    return f[u] == u ? u : f[u] = Find(f[u]);
}
void Union(int u,int v)
{
    int fv = Find(v);
    int fu = Find(u);
    if(fv != fu)
    {
        f[fv] = fu;
    }
}
void Merge(int u)
{
    int x = Find(u);
    int y = Find(fa[u]);

    if(x != y)
    {
        bridge--;
        f[x] = y;
    }
}
void addedge(int u,int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    edge[tot].cut = false;
    head[u] = tot++;
}
void Tarjan(int u,int pre)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    fa[u] = pre;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if(v == pre)
            continue;
        if( !DFN[v] )
        {
            Tarjan(v,u);
            if(Low[u] > Low[v])
                Low[u] = Low[v];
//桥
//一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。
            if(Low[v] > DFN[u])
            {
                bridge++;
                edge[i].cut = true;
                edge[i^1].cut = true;
            }
            else
            {
                Union(u,v);
            }
        }
        else if( Low[u] > DFN[v])
            Low[u] = DFN[v];
    }
}
void solve(int N)
{
    memset(DFN,0,sizeof(DFN));
    memset(fa,0,sizeof(fa));
    Index = bridge = 0;
    for(int i =1; i<=N; i++)
    {
        f[i] = i;
    }
    for(int i = 1; i <= N; i++)
        if( !DFN[i] )
            Tarjan(i,i);
}

void lca(int u,int v)
{
    while(DFN[u] > DFN[v])
    {
        Merge(u);
        u = fa[u];
    }
    while(DFN[v] > DFN[u])
    {
        Merge(v);
        v = fa[v];
    }
    while(u != v)
    {
        Merge(u);
        Merge(v);
        u = fa[u];
        v = fa[v];
    }
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
int main()
{
    int n,m;
    int cat  = 1;
    while(~scanf("%d%d",&n,&m),n||m)
    {
        init();
        for(int i = 1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        solve(n);
        int q;
        scanf("%d",&q);
        printf("Case %d:\n",cat++);
        while(q--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            lca(u,v);
            printf("%d\n",bridge);
        }
        printf("\n");
    }

    return 0;
}

POJ - 3177 SCC缩点

这道题的题意是如果现在有一颗树(或者干脆就是联通图),要让两个点之间至少有两条不一样的边,问最少添加的个数。
首先我们缩点。缩点之后每个块内的点不言而喻是满足要求的,那么对于一个连通图,我们要让所有点之间有两条路径,只要让度数为1的点之间连一条边也就是(度为1的点+1)/2.

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int MAXN = 5010;//点数
const int MAXM = 20010;//边数,因为是无向图,所以这个值要*2
struct Edge
{
    int to,next;
    bool cut;//是否是桥标记
} edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~block
int Index,top;
int block;//边双连通块数
bool Instack[MAXN];
int bridge;//桥的数目
void addedge(int u,int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    edge[tot].cut=false;
    head[u] = tot++;
}
void Tarjan(int u,int pre)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if(v == pre)
            continue;
        if( !DFN[v] )
        {
            Tarjan(v,u);
            if( Low[u] > Low[v] )
                Low[u] = Low[v];
            if(Low[v] > DFN[u])
            {
                bridge++;
                edge[i].cut = true;
                edge[i^1].cut = true;
            }
        }
        else if( Instack[v] && Low[u] > DFN[v] )
            Low[u] = DFN[v];
    }
    if(Low[u] == DFN[u])
    {
        block++;
        do
        {
            v = Stack[--top];
            Instack[v] = false;
            Belong[v] = block;
        }
        while( v!=u );
    }
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
int du[MAXN];//缩点后形成树,每个点的度数
void solve(int N)
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    //memset(add_block,0,sizeof(add_block));
    //memset(cut,false,sizeof(cut));
    Index = top = 0;
    bridge = block = 0;
    Tarjan(1,0);
    memset(du,0,sizeof(du));
    for(int i  =1;i<=N;i++)
    {
        for(int j = head[i];j != -1;j = edge[j].next)
        {
            if(edge[j].cut)
            {
                du[Belong[i]]++;
            }
        }
    }
    int ans = 0;
    for(int i = 1;i<=block;i++)
    {
        if(du[i] == 1)
        {
            ans ++;
        }
    }
    printf("%d\n",(ans + 1) / 2);

}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i = 1; i<=m; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        solve(n);

    }

    return 0;
}

找桥 + 求树的直径 HDU - 4612

这道题的题意很简单,就是问你加一条边最后剩下的最小的桥数。我们先考虑一下缩点之后的情况。缩点之后会形成一棵树(桥作树边),那么我们在其中加一条边就能在树上连出一个环,环上所有的点被缩成了一个点。那么我们就想让这个环的长度尽可能地大,因为剩下的桥,也就是答案是最优的。所以我们只要找到这棵树上的直径,。把直径首尾连接,答案就是(桥数 - 直径 -1)

除此之外还有重边的问题,我们可以暴力判断重边,在添加边的时候加入一个判重标记,在tarjan过程中避开这个边就行。

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 200010;//点数
const int MAXM = 2000100;//边数,因为是无向图,所以这个值要*2
struct Edge
{
    int to,next;
    bool cut;//是否是桥标记
    bool red;
} edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~block
int Index,top;
int block;//边双连通块数
bool Instack[MAXN];
int bridge;//桥的数目
void addedge(int u,int v,bool r)
{
    edge[tot].to = v;
    edge[tot].red = r;
    edge[tot].next = head[u];
    edge[tot].cut=false;
    head[u] = tot++;
}
void Tarjan(int u,int pre,bool re)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if(v == pre && !re)
            continue;
        if( !DFN[v] )
        {
            Tarjan(v,u,edge[i].red);
            if( Low[u] > Low[v] )
                Low[u] = Low[v];
            if(Low[v] > DFN[u])
            {
                bridge++;
                edge[i].cut = true;
                edge[i^1].cut = true;
            }
        }
        else if( Instack[v] && Low[u] > DFN[v] )
            Low[u] = DFN[v];
    }
    if(Low[u] == DFN[u])
    {
        block++;
        do
        {
            v = Stack[--top];
            Instack[v] = false;
            Belong[v] = block;
        }
        while( v!=u );
    }
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
vector<int> vec[MAXN];
int dep[MAXN];
void dfs(int u)
{
    for(int i = 0;i<vec[u].size();i++)
    {
        int v = vec[u][i];
        if(dep[v] == -1)
        {
            dep[v] = dep[u]+1;
            dfs(v);
        }
    }
}
void solve(int N)
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    Index = top = 0;
    bridge = block = 0;
    Tarjan(1,0,false);
    for(int i = 1;i<=block;i++)
    {
        vec[i].clear();
    }
    for(int i  =1;i<=N;i++)
    {
        for(int j = head[i];j != -1;j = edge[j].next)
        {
            if(edge[j].cut)
            {
                vec[Belong[i]].push_back(Belong[edge[j].to]);
            }
        }
    }
    memset(dep,-1,sizeof(dep));
    dep[1] = 0;
    dfs(1);
    int s = 1;
    for(int i = 1;i<=block;i++)
    {
        if(dep[s] < dep[i])
        {
            s = i;
        }
    }
    memset(dep,-1,sizeof(dep));
    dep[s] = 0;
    dfs(s);
    int ans = 0;
    for(int i = 1;i<=block;i++)
    {
        ans = max(ans,dep[i]);
    }
   // printf("%d\n",ans);
    printf("%d\n",block-ans-1);
}
struct Node
{
    int u,v;
}node[MAXM];
int cmp(Node a,Node b)
{
    if(a.u != b.u) return a.u < b.u;
    else return a.v < b.v;
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        if(n == 0) break;
        init();
        for(int i = 0; i<m; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            if(u == v) continue;
            if(u > v) swap(u,v);

            node[i].u = u;
            node[i].v = v;
        }
        sort(node,node+m,cmp);
        for(int i =0;i<m;i++)
        {
            if(i == 0 || (node[i].u != node[i-1].u || node[i].v != node[i-1].v))
            {
                if(i < m-1&& (node[i].u == node[i+1].u && node[i].v == node[i+1].v))
                {
                    addedge(node[i].u,node[i].v,true);
                    addedge(node[i].v,node[i].u,true);
                }
                else
                {
                    addedge(node[i].u,node[i].v,false);
                    addedge(node[i].v,node[i].u,false);
                }
            }
        }
        solve(n);
    }

    return 0;
}

强连通 + 思维 HDU 4635
这道题的题意很明确,在一个有向简单图中添加最多的边,使得图还是一个简单图,且不是强连通图。
如果只有一个强连通分量,那没有多余的边可以添加。如可以添加,那么一定可以把图分成X,Y两部分,这两部分各自是完全图,而且只有X到Y中每个点的有向边。答案就是n*(n-1) - m - X*Y 。为了让这个答案最大,我们还要找到符合要求的X点数最小的那个(x +y = n) ,符合要求的点首先需要出度或者入度为0的点,如果有一个不为0,都无法满足我们设想的条件。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <map>
#include <cstring>
#define ll long long
using namespace std;
const int MAXN = 1e5+ 5;//点数
const int MAXM = 2e5 + 5;//边数
struct Edge
{
int to,next;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~scc
int Index,top;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量
void addedge(int u,int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void Tarjan(int u)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if( !DFN[v] )
        {
            Tarjan(v);
            if( Low[u] > Low[v] )
                Low[u] = Low[v];
        }
        else if(Instack[v] && Low[u] > DFN[v])
            Low[u] = DFN[v];
    }
    if(Low[u] == DFN[u])
    {
        scc++;
        do
        {
            v = Stack[--top];
            Instack[v] = false;
            Belong[v] = scc;
            num[scc]++;
        }
        while( v != u);
    }
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
void solve(int N)
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    memset(num,0,sizeof(num));
    Index = scc = top = 0;
    for(int i = 1; i <= N; i++)
        if(!DFN[i])
            Tarjan(i);

}
int in[MAXN],out[MAXN];
int main()
{
    int ca,cat = 1;
    scanf("%d",&ca);
    while(ca--)
    {
        int n,m;
        init();
        scanf("%d%d",&n,&m);
        for(int i = 1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        solve(n);
        printf("Case %d: ",cat++);
        if(scc ==  1)
        {
            printf("-1\n");
        }
        else
        {
            memset(in,0,sizeof(in));
            memset(out,0,sizeof(out));
            for(int i = 1;i<=n;i++)
            {
                for(int j = head[i] ; j !=-1 ;j = edge[j].next)
                {
                    int v = edge[j].to;
                    if(Belong[v] == Belong[i]) continue;
                    out[Belong[i]] ++;
                    in[Belong[v]] ++;
                }
            }
            ll sum = (ll)n*(n-1) - m;
            ll ans = 0;
            for(int i = 1;i<=scc;i++)
            {
                if(in[i] == 0 || out[i] == 0)
                {
                    ans = max(ans,sum - (ll)num[i]*(n - num[i]));
                }
            }
            printf("%lld\n",ans);
        }

    }
    //system("pause");
    return 0;
}

建图神题! HDU - 4685

这到题有点不太好想,首先我们要求完美匹配,求出完美匹配之后,我们把二分图左边的王子i匹配到的,连接和他匹配的那个公主和其他匹配的公主连一条边。然后求强连通分量。那么在一个强连通分量里的公主就都可供选择,原因是,她们最后匹配到的还是原来的i王子。
那么问题来了,怎么求完美匹配呢?我们先求出来最大匹配res,说明左边有n- res,右边有m - res没有匹配到,我们就在左边添加m - res个点,右边添加n -res个点,这些新添加的点和对面的点都连一条边,这样就都匹配了

https://www.cnblogs.com/kuangbin/p/3261157.html

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN = 1010;
int uN,vN;//u,v的数目,使用前面必须赋值
int g[MAXN][MAXN];//邻接矩阵
int linker[MAXN];
bool used[MAXN];
int relink[MAXN];
bool dfs(int u)
{
    for(int v = 0; v < vN; v++)
        if(g[u][v] && !used[v])
        {
            used[v] = true;
            if(linker[v] == -1 || dfs(linker[v]))
            {
                linker[v] = u;
                return true;
            }
        }
    return false;
}
int hungary()
{
    int res = 0;
    memset(linker,-1,sizeof(linker));
    for(int u = 0; u < uN; u++)
    {
        memset(used,false,sizeof(used));
        if(dfs(u))
            res++;
    }
    return res;
}
const int MAXM = 500100;//边数
struct Edge
{
    int to,next;
} edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~scc
int Index,top;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
//num数组不一定需要,结合实际情况
void addedge(int u,int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
void Tarjan(int u)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if( !DFN[v] )
        {
            Tarjan(v);
            if( Low[u] > Low[v] )
                Low[u] = Low[v];
        }
        else if(Instack[v] && Low[u] > DFN[v])
            Low[u] = DFN[v];
    }
    if(Low[u] == DFN[u])
    {
        scc++;
        do
        {
            v = Stack[--top];
            Instack[v] = false;
            Belong[v] = scc;
            num[scc]++;
        }
        while( v != u);
    }
}
void solve(int N)
{
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    memset(num,0,sizeof(num));
    Index = scc = top = 0;
    for(int i = 1; i <= N; i++)
        if(!DFN[i])
            Tarjan(i);
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
int main()
{
    int n,m;
    int ca,cat = 1;
    scanf("%d",&ca);
    while(ca--)
    {
        scanf("%d%d",&n,&m);
        init();
        memset(g,0,sizeof(g));
        for(int i = 1;i<=n;i++)
        {
            int k;
            scanf("%d",&k);
            while(k--)
            {
                int x;
                scanf("%d",&x);
                g[i][x] = 1;
            }
        }
        uN = n;
        vN = m;
        int res = hungary();
        uN = vN = n+m- res;
        for(int i = n+1;i<=uN;i++ )
        {
            for(int j = 1;j<=vN;j++)
            {
                g[i][j] = 1;
            }
        }
       for(int i =1;i<=uN;i++)
       {
            for(int j = m+1;j<=vN;j++)
            {
                g[i][j] = 1;
            }
       }
        hungary();
        memset(relink,-1,sizeof(relink));
        for(int i = 1;i<=vN;i++)
        {
            if(linker[i] != -1)
            {
                relink[linker[i]] = i;
            }
        }
        for(int i = 1;i<=uN;i++)
        {
            for(int j =1;j<=vN;j++)
            {
                if(g[i][j] && relink[i] != j)
                {
                    addedge(relink[i],j);
                }
            }
        }
        solve(vN);
        printf("Case #%d:\n",cat++);
        vector<int> ans;
        for(int i = 1;i<=n;i++)
        {
            ans.clear();
            for(int j = 1;j<=m;j++)
            {
                if(g[i][j] && Belong[j] == Belong[relink[i]])
                {
                    ans.push_back(j);
                }
            }
            printf("%d",(int)ans.size());
            for(int j = 0;j<ans.size();j++)
            {
                printf(" %d",ans[j]);
            }
            printf("\n");
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lingzidong/article/details/80737985