kuangbin专题九连通图总结

总结:坐到后面,才感觉tarjan算法其实是一种思想,在不同情况下需要灵活运用。
本专题所涉及的内容比较多,每个题目的类型都比较经典,所以每个题都感觉很重要。
注意:
1,有的时候题目并没有说清楚有没有重边,有需要的话就在tarjan里面加一点判断。
2,缩点的方式有很多,在tarjan的时候可以缩,也可以tarjan之后用并查集缩。
3,感觉连通图的题的代码有点长,慢慢写别写错就好。。

A - Network of Schools
问题描述:给一个有向图,A问题问至少选多少个点可以到达所有的点。B问题询问至少加多少条边可以使得这个图是强连通图。
首先解决A问题,我们可以用tarjan算法来算出各个连通分量,然后再计算每个连通分量的入度和出度,那么入度等于0的连通分量的个数也就是A问题的答案。问题B我们则取入度为0的个数和出度为0的个数中的最大值即可。我们可以这样分析:将一个出度为0的点a连向一个入度为0的点b,并且b是a的祖先(b可以到a),那么他们就组成了一个环,这条路径上的点也就强连通了,那么我们如果错开相连,a连向一个入度为0的点c(c不是a的祖先),再用c可到达的出度为0的点d连向b的话,他们就组成了一个环,也就变成强连通了,基于这样的连法,所要加的边的条数就是入度为0的个数和出度为0的个数的最大值了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 105;
struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
    Edge(){}
}edges[maxn*maxn];
int head[maxn],tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}

int DFN[maxn], LOW[maxn],vis[maxn],id;
int belong[maxn], cnt;
int stack[maxn],tot1;

void tarjan(int u)
{
    DFN[u] = LOW[u] = ++id;
    stack[tot1++] = u;
    vis[u] = 1;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (!DFN[v])
        {
            tarjan(v);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if (vis[v])
            LOW[u] = min(LOW[u], DFN[v]);
    }

    if (LOW[u] == DFN[u])
    {
        cnt++;
        int v;
        do
        {
            v = stack[tot1 - 1];
            vis[v] = 0;
            belong[v] = cnt;
            tot1--;
        } while (v!=u);
    }

}
int in[maxn], out[maxn];
int main()
{
    memset(head, -1, sizeof(head));
    int n;
    scanf("%d", &n);
    int tmp;
    for (int i = 1;i <= n;i++)
    {
        while (~scanf("%d",&tmp)&&tmp)
        {
            addedge(i, tmp);
        }
    }
    for (int i = 1;i <= n;i++)if (!DFN[i])
        id=0,tarjan(i);

    for (int i = 1;i <= n;i++)
    {
        for (int j = head[i];~j;j = edges[j].next)
        {
            if (belong[i] != belong[edges[j].v])
                out[belong[i]]++, in[belong[edges[j].v]]++;
        }
    }
    int one = 0, two = 0;
    for (int i = 1;i <= cnt;i++)
    {
        if (!in[i])one++;
        if (!out[i])two++;
    }

    if (cnt == 1)
    {
        cout << 1 << endl;
        cout << 0 << endl;
        return 0;
    }

    cout << one << endl;
    cout << max(one, two) << endl;
}

B - Network
问题描述:求割点。
所谓割点就是将这个点及与这个点相连的边都删掉的话,能使强连通分量增多的点。
一个点是割点有两个判断条件。
1,如果它是根的话,它的分支>1。
2,如果不是根,DFN[fa[u]]<=LOW[u]。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 105;

struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
    Edge(){}
}edges[maxn*maxn];
int head[maxn], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}

int DFN[maxn], LOW[maxn], id;
int N;
bool chose[maxn];
int fa[maxn];
void tarjan(int u, int pre)
{
    fa[u] = pre;
    DFN[u] = LOW[u] = ++id;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (!DFN[v])
        {
            tarjan(v, u);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if (v != pre)
            LOW[u] = min(LOW[u], DFN[v]);
    }
}

void init()
{
    id = 0;
    tot = 0;
    memset(head, -1, sizeof(head));
    memset(DFN, 0, sizeof(DFN));
    memset(LOW, 0, sizeof(LOW));
    memset(chose, 0, sizeof(chose));
}

void solve()
{
    tarjan(1, 0);
    int rootnum = 0;
    for (int i = 1;i <= N;i++)
    {
        if (fa[i] == 1)rootnum++;
        else if (LOW[i] >= DFN[fa[i]])
            chose[fa[i]] = 1;
    }
    int ans = 0;
    for (int i = 2;i <= N;i++)
        if (chose[i])
            ans++;
    if (rootnum > 1)
        ans++;
    printf("%d\n", ans);
}

int main()
{

    while (~scanf("%d",&N)&&N)
    {
        init();
        char s;
        int tmp;
        while (1)
        {
            scanf("%d%c", &tmp, &s);
            if (!tmp)break;
            int tmp2;
            while (~scanf("%d%c",&tmp2,&s))
            {
                addedge(tmp, tmp2);
                addedge(tmp2, tmp);
                if (s == '\n')break;
            }
        }
        solve();
    }
}

C - Critical Links
问题描述:求桥。桥的定义:删掉这条边会使强连通分量增多。
桥的判断只有1个。
当DFN[fa[u]] < LOW[u]时,这条边就是桥。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<set>
using namespace std;
const int maxn = 505;
struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _n):u(_u),v(_v),next(_n){}
    Edge(){}
}edges[maxn*maxn];
int head[maxn], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}

int deg[maxn];
int DFN[maxn], LOW[maxn], id;
int n;
pair<int, int>pii[maxn*maxn];
int pcnt;
int fa[maxn];

void init()
{
    tot = 0;
    id = 0;
    memset(head, -1, sizeof(head));
    memset(deg, 0, sizeof(deg));
    memset(DFN, 0, sizeof(DFN));

    pcnt = 0;
}

void tarjan(int u,int pre)
{
    DFN[u] = LOW[u] = ++id;
    fa[u] = pre;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (!DFN[v])
        {
            tarjan(v, u);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if (v != pre)
            LOW[u] = min(LOW[u], DFN[v]);
    }
}

void solve()
{
    for (int i = 0;i < n;i++)if(!DFN[i])
    {
        id = 0;
        tarjan(i,-1);
    }
    int ans = 0;
    for (int i = 0;i < n;i++)
    {
        if (fa[i] == -1)continue;
        if (DFN[fa[i]] < LOW[i])
        {
            ans++;
            int themin = min(fa[i], i);
            int themax = max(fa[i], i);
            pii[pcnt++] = make_pair(themin, themax);
        }
    }
    sort(pii, pii + pcnt);
    printf("%d critical links\n", ans);
    for (int i = 0;i < pcnt;i++)
        printf("%d - %d\n", pii[i].first, pii[i].second);
    puts("");
}


int main()
{

    while (~scanf("%d",&n))
    {
        init();
        int u;
        for (int i = 1;i <= n;i++)
        {
            scanf("%d", &u);
            char s[100];
            scanf("%s", s);
            int tmp;
            sscanf(s, "(%d)", &tmp);
            int v;
            while (tmp--)
            {
                scanf("%d", &v);
                deg[u]++, deg[v]++;
                addedge(u, v);
                addedge(v, u);
            }
        }
        solve();
    }

}

D - Network
问题描述:给一个无向图,每次询问加一条边,问图中还有多少个桥。
首先我们可以把桥算出来,注意我们在用tarjan的时候,其实把这个图按照时间戳的顺序变成了一个树,那么再加u到v的边的时候其实就是就相当于销毁树上u点到v点这条路上的所有的桥。这里我们其实可以用并查集优化一下求lca的过程。

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100005;
const int maxm = 200005;
struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
    Edge(){}
}edges[maxm*2];
int head[maxn], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}

int DFN[maxn], LOW[maxn], dep[maxn],fa[maxn], id;
int cut[maxn];
int f[maxn];
int N, M;
void init()
{
    tot = 0;id = 0;
    memset(head, -1, sizeof(head));
    memset(DFN, 0, sizeof(DFN));
    memset(dep, 0, sizeof(dep));
    memset(cut, 0, sizeof(cut));
    for (int i = 1;i <= N;i++)
        f[i] = i;
}


void tarjan(int u,int pre)
{
    DFN[u] = LOW[u] = ++id;
    fa[u] = pre;
    dep[u] = dep[pre] + 1;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (!DFN[v])
        {
            tarjan(v, u);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if(v!=pre)
            LOW[u] = min(LOW[u], DFN[v]);
    }
}
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
int solve()
{
    tarjan(1, 0);
    int ans = 0;
    for (int i = 2;i <= N;i++)
    {
        if (DFN[fa[i]] < LOW[i])
        {
            cut[i] = 1, ans++;
        }
        else
        {
            int u = find(i), v = find(fa[i]);
            f[u] = v;
        }
    }
    return ans;
}




int query(int u, int v)
{
    int ans = 0;
    u = find(u), v = find(v);
    while (u!=v)
    {
        if (dep[u] > dep[v])
        {

            int fu = find(fa[u]);
            f[u] = fu;
            u = fu;
        }
        else
        {
            int fv = find(fa[v]);
            f[v] = fv;
            v = fv;
        }
        ans++;
    }
    return ans;

}

int main()
{
    int cas = 1;
    while (~scanf("%d %d",&N,&M)&&N)
    {
        init();
        int u, v;
        for (int i = 1;i <= M;i++)
        {
            scanf("%d %d", &u, &v);
            addedge(u, v);
            addedge(v, u);
        }
        int ans=solve();
        int q;

        scanf("%d", &q);
        printf("Case %d:\n", cas++);
        while (q--)
        {
            scanf("%d %d", &u, &v);
            ans -= query(u, v);
            printf("%d\n", ans);
        }
        puts("");
    }
    return 0;
}

E - Redundant Paths
问题描述:给一个无向图,问至少加多少条边使得图上没有桥。
我们可以在tarjan的时候处理一下,将不是桥的边连的点缩一下。然后我们再求缩完点之后的每个点的度,记度为1的点的数量为num,那么ans=(num+1)/2。读者可以自行想一想为什么。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
using namespace std;
const int maxn = 5005;
const int maxm = 5005;
struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
    Edge(){}
}edges[maxm * 2];
int head[maxn], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}
int DFN[maxn], LOW[maxn], vis[maxn], belong[maxn], fa[maxn], id;
int kind;
int stack[maxn], cnt;
void tarjan(int u, int pre)
{
    DFN[u] = LOW[u] = ++id;
    fa[u] = pre;
    vis[u] = 1;
    stack[cnt++] = u;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (v == pre)continue;
        if (!DFN[v])
        {
            tarjan(v, u);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if (vis[v] == 1)
        {
            LOW[u] = min(LOW[u], DFN[v]);
        }
    }

    if (LOW[u] == DFN[u])
    {
        kind++;
        int v;
        do
        {
            v = stack[cnt - 1];
            belong[v] = kind;
            cnt--;
            vis[v] = 0;
        } while (v!=u);
    }
}

int deg[maxn];
int n, m;
void solve()
{
    tarjan(1, 0);
    for (int i = 2;i <= n;i++)
    {
        int a = belong[i], b = belong[fa[i]];
        if (a != b)
            deg[a]++, deg[b]++;
    }
    int ans = 0;
    for (int i = 1;i <= kind;i++)
        if (deg[i] == 1)
            ans++;
    printf("%d\n", (ans + 1) / 2);
}


int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d %d", &n, &m);
    int u, v;
    for (int i = 1;i <= m;i++)
    {
        scanf("%d %d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    solve();
}

F - Warm up
问题描述:给一个无向图,加一条边使得图的桥最少,问最少可以是多少。
我们仍然可以先求出桥,然后进行缩点。对缩完点之后的树求一下直径就行了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<stdio.h>
#include<queue>
using namespace std;
const int maxn = 200005;
const int maxm = 1000005;
struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
    Edge(){}
}edges[maxm * 2];
int head[maxn], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}
int cut[maxn];
int f[maxn];
int DFN[maxn], LOW[maxn], fa[maxn], id;
int n, m;
void init()
{
    tot = 0;
    id = 0;
    memset(head, -1, sizeof(head));
    memset(cut, 0, sizeof(cut));
    memset(DFN, 0, sizeof(DFN));
}


void tarjan(int u, int pre)
{
    DFN[u] = LOW[u] = ++id;
    fa[u] = pre;
    bool flag = true;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (v == pre&&flag) { flag = false;continue; }
        if (!DFN[v])
        {
            tarjan(v, u);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else
            LOW[u] = min(LOW[u], DFN[v]);
    }
}

int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }

void change(int u)
{
    while (u!=1&&!cut[u])
    {
        int f2 = find(fa[u]);
        f[u] = f2;
        u = f2;
    }
}
int d[maxn];
struct node
{
    int u, dist;
    node(int _u,int _d):u(_u),dist(_d){}
    node(){}
    bool operator<(const node &b)const
    {
        return dist < b.dist;
    }
};
bool vis[maxn];
void bfs(int u)
{
    memset(vis, 0, sizeof(vis));
    priority_queue<node>Q;
    Q.push(node(u, 0));
    while (!Q.empty())
    {
        node x = Q.top();Q.pop();
        if (vis[x.u])continue;
        vis[x.u] = 1;
        for (int i = head[x.u];~i;i = edges[i].next)
        {
            int v = edges[i].v;
            if (d[v] < d[x.u] + 1)
            {
                d[v] = d[x.u] + 1;
                Q.push(node(v, d[v]));
            }
        }
    }
}

void solve()
{
    tarjan(1, 0);
    int num = 0;
    for (int i = 2;i <= n;i++)
    {
        int f = fa[i];
        if (DFN[f] < LOW[i])
        {
            num++;
            cut[i] = 1;
        }
    }
    for (int i = 1;i <= n;i++)f[i] = i;
    for (int i = 1;i <= n;i++)if (f[i] == i)
        change(i);
    memset(head, -1, sizeof(head));
    tot = 0;
    int edgenum = 0;
    for (int i = 2;i<= n;i++)if (cut[i])
    {
        edgenum++;
        int u = i, v = fa[i];
        int fu = find(u), fv = find(v);
        addedge(fu, fv);
        addedge(fv, fu);
    }
    for (int i = 1;i <= n;i++)
        d[i] = 0;
    bfs(1);
    int themax = 0, idx;
    for(int i=1;i<=n;i++)
        if (d[i] > themax)
        {
            themax = d[i];
            idx = i;
        }
    for (int i = 1;i <= n;i++)
        d[i] = 0;
    bfs(idx);
    themax = 0;
    for (int i = 1;i <= n;i++)
        if (d[i] > themax)
        {
            themax = d[i];
            idx = i;
        }
    printf("%d\n", edgenum - themax);
}

int main()
{

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

}

G - Strongly connected
问题描述:给一个简单的有向图,问最多可以加多少条边,使得图仍然不是强连通图。
最终添加完边的图,肯定可以分成两个部X和Y,其中只有X到Y的边没有Y到
X的边,那么要使得边数尽可能的多,则X部肯定是一个完全图,Y部也是,同时X部
中每个点到Y部的每个点都有一条边,假设X部有x个点,Y部有y个点,有x+y=n,同时
边数F=x*y+x*(x-1)+y*(y-1),整理得:F=N*N-N-x*y,(然后去掉已经有了的边m,就是
答案),当x+y为定值时,二者越接近,x*y越大,所以要使得边数最多,那么X部和Y部
的点数的个数差距就要越大,所以首先对于给定的有向图缩点,对于缩点后的每个点,
如果它的出度或者入度为0,那么它才有可能成为X部或者Y部,所以只要求缩点之后的
出度或者入度为0的点中,包含节点数最少的那个点,令它为一个部,其它所有点加起
来做另一个部,就可以得到最多边数的图了。
思路转自:
http://www.cnblogs.com/jackge/p/3231767.html

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 100005;
const int maxm = 100005;
typedef long long ll;
struct Edge
{
    int u, v, next;
    Edge(int _u,int _v,int _nxt):u(_u),v(_v),next(_nxt){}
    Edge(){}
}edges[maxm];
int head[maxn], tot;
void addedge(int u, int v)
{
    edges[tot] = Edge(u, v, head[u]);
    head[u] = tot++;
}

int DFN[maxn], LOW[maxn], vis[maxn], id;
int n, m;
int stack[maxn], cnt;
int belong[maxn], bcnt;
int num[maxn];
int in[maxn], out[maxn];
void init()
{
    tot = 0;
    memset(head, -1, sizeof(head));
    memset(DFN, 0, sizeof(DFN));
    id = 0;bcnt = 0;
    memset(num, 0, sizeof(num));
    memset(in, 0, sizeof(in));
    memset(out, 0, sizeof(out));
}
void tarjan(int u)
{
    DFN[u] = LOW[u] = ++id;
    vis[u] = 1;
    stack[cnt++] = u;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (!DFN[v])
        {
            tarjan(v);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if (vis[v])
            LOW[u] = min(LOW[u], DFN[v]);
    }
    if (LOW[u] == DFN[u])
    {
        int v;
        bcnt++;
        do
        {
            v = stack[cnt - 1];
            cnt--;
            vis[v] = 0;
            belong[v] = bcnt;
        } while (v!=u);
    }
}

void solve()
{
    for (int i = 1;i <= n;i++)if (!DFN[i])
        id=0,tarjan(i);
    for (int i = 1;i <= n;i++)
        num[belong[i]]++;
    for (int i = 1;i <= n;i++)
    {
        for (int j = head[i];~j;j = edges[j].next)
        {
            int v = edges[j].v;
            if (belong[i] != belong[v])
                out[belong[i]]++, in[belong[v]]++;
        }
    }
    int Y = n;
    for (int i = 1;i <= bcnt;i++)if(in[i]==0||out[i]==0)
    {
        Y = min(Y, num[i]);
    }
    if (Y == n)
    {
        puts("-1");
        return;
    }
    ll ans = 1LL * n*n - n - 1LL * (n - Y)*Y - m;
    printf("%lld\n", ans);
}

int main()
{
    int T;
    scanf("%d",&T);
    int cas = 1;
    while (T--)
    {

        scanf("%d %d", &n, &m);
        init();
        int u, v;
        for (int i = 1;i <= m;i++)
        {
            scanf("%d %d", &u, &v);
            addedge(u, v);
        }
        printf("Case %d: ", cas++);
        solve();
    }
}

I - Caocao’s Bridges
问题描述:求桥边权最小是多少。
这题有几个坑点。。
1,如果一开始就不是强连通图,那么输出0。
2,如果边权最小为0,那么应该输出1,因为要派1个人去。。。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 1005;
const int maxm = maxn*maxn;
struct Edge
{
    int u, v,cost, next;
    Edge(int _u,int _v,int _c,int _nxt):u(_u),v(_v),cost(_c),next(_nxt){}
    Edge(){}
}edges[maxm * 2];
int head[maxn], tot;
void addedge(int u, int v,int c)
{
    edges[tot] = Edge(u, v, c,head[u]);
    head[u] = tot++;
}
int DFN[maxn], LOW[maxn], id;
int ans;
void init()
{
    ans = maxm;
    tot = 0, id = 0;
    memset(head, -1, sizeof(head));
    memset(DFN, 0, sizeof(DFN));
}

void tarjan(int u, int pre)
{
    DFN[u] = LOW[u] = ++id;
    bool flag = true;
    for (int i = head[u];~i;i = edges[i].next)
    {
        int v = edges[i].v;
        if (v == pre&&flag) { flag = false;continue; }
        if (!DFN[v])
        {
            tarjan(v, u);
            LOW[u] = min(LOW[u], LOW[v]);
            if (DFN[u] < LOW[v])
                ans = min(ans, edges[i].cost);
        }
        else
            LOW[u] = min(LOW[u], DFN[v]);
    }
}


int n, m;
void solve()
{
    tarjan(1, 0);

    for (int i = 1;i <= n;i++)if (DFN[i] == 0) { puts("0");return; }

    if (ans == maxm)puts("-1");
    else if (ans == 0)puts("1");
    else printf("%d\n", ans);
}


int main()
{

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

}

H - Prince and Princess
思路转自这里
主要构图转化的思路很神奇,我看了题解才会的,T_T
首先n,m个点做一次二分匹配,得到匹配数最大为res. 相当于右边有m-res个点没有匹配,左边有n-res个点没有匹配。
所以在左加m-res个点,和右边所有相连。
在右边加n-res个点,个左边所有相连。
然后做n+m-res,n+m-res个点的二分匹配。
匹配数肯定是n+m-res;
主要是得到匹配的情况。
对于左边的点i. 把i相匹配的在右边的点,向其余和i相连的点连一有向边。
然后做强连通缩点。
如果边v1,v2在同一个连通分量里,那么v1,v2匹配的u1,u2可以交换。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 1005;
const int inf = 0x3f3f3f3f;
bool map[maxn][maxn];

int cx[maxn], cy[maxn];
int dx[maxn], dy[maxn];
bool used[maxn];
int dist;
int n, m;
int nL, nR;
bool bfs()
{
    queue<int>Q;
    memset(dy, -1, sizeof(dy));
    memset(dx, -1, sizeof(dx));
    for (int i = 1;i <= nL;i++)if (cx[i] == -1)
        Q.push(i), dx[i] = 0;
    dist = inf;

    while (!Q.empty())
    {
        int u = Q.front();Q.pop();
        if (dx[u] > dist)break;
        for (int i = 1;i <= nR;i++)if (map[u][i] && dy[i]==-1)
        {
            dy[i] = dx[u] + 1;
            if (cy[i] == -1)dist = dy[i];
            else
            {
                dx[cy[i]] = dy[i] + 1;
                Q.push(cy[i]);
            }
        }
    }
    return dist != inf;
}

int find(int u)
{
    for (int i = 1;i <= nR;i++)if (map[u][i] && !used[i]&&dy[i]==dx[u]+1)
    {
        used[i] = 1;
        if (cy[i] != -1 && dy[i] == dist)continue;
        else if (cy[i] == -1 || find(cy[i]))
        {
            cx[u] = i;
            cy[i] = u;
            return 1;
        }
    }
    return 0;
}

int MaxMatch()
{
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    int ans = 0;
    while (bfs())
    {
        memset(used, 0, sizeof(used));
        for (int i = 1;i <= nL;i++)
            if (cx[i] == -1)
                ans += find(i);
    }
    return ans;
}
int DFN[maxn], LOW[maxn],id;
int stack[maxn], s;
int belong[maxn], bnum;
bool vis[maxn];
int link[maxn][maxn];

void tarjan(int u)
{
    DFN[u] = LOW[u] = ++id;
    stack[s++] = u;
    vis[u] = 1;
    for (int i = 1;i <= nR;i++)if (link[u][i])
    {
        if (!DFN[i])
        {
            tarjan(i);
            LOW[u] = min(LOW[u], LOW[i]);
        }
        else if(vis[i])
            LOW[u] = min(LOW[u], DFN[i]);
    }
    if (DFN[u] == LOW[u])
    {
        bnum++;
        int v;
        do
        {
            v = stack[s - 1];
            vis[v] = 0;
            s--;
            belong[v] = bnum;
        } while (v!=u);
    }
}

void solve()
{
    for (int i = 1;i <= nR;i++)if (!DFN[i])
        id=0,tarjan(i);
}
vector<int>V;

void init()
{
    memset(DFN, 0, sizeof(DFN));
    memset(map, 0, sizeof(map));
    memset(link, 0, sizeof(link));
    id = 0;bnum = 0;
}

int main()
{
    int T;
    scanf("%d", &T);
    int cas = 1;
    while (T--)
    {

        scanf("%d %d", &n, &m);
        init();
        int tmp;
        for (int i = 1;i <= n;i++)
        {
            int times;
            scanf("%d", &times);
            while (times--)
            {
                scanf("%d", &tmp);
                map[i][tmp] = 1;
            }
        }
        nL = n, nR = m;
        int res = MaxMatch();
        int num = n + m - res;
        nL = nR = num;
        for (int i = n + 1;i <= num;i++)
            for (int j = 1;j <= num;j++)
                map[i][j] = 1;
        for (int i = 1;i <= n;i++)
            for (int j = m + 1;j <= num;j++)
                map[i][j] = 1;
        MaxMatch();
        for (int i = 1;i <= num;i++)
        {
            int to = cx[i];
            for (int j = 1;j <= num;j++)if(map[i][j])
                link[to][j] = 1;
        }
        solve();
        printf("Case #%d:\n", cas++);
        for (int i = 1;i <= n;i++)
        {
            V.clear();
            int to = cx[i];
            int blong = belong[to];
            for (int j = 1;j <= m;j++)if (map[i][j] && belong[j] == blong)
                V.push_back(j);
            printf("%d", V.size());
            for (int i = 0;i < V.size();i++)
                printf(" %d", V[i]);
            puts("");
        }

    }
    return 0;

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值