[kuangbin带你飞]专题九 连通图 题解报告

专题链接
关于tarjan

A - Network of Schools  原题地址

本题有2个问题,第一个是要算最少要给多少个点软件,才能使所有点都可以收到副本
第二个是要算最少加多少条边,使得图变成强连通

1:tarjan求强连通,然后缩点,计算入度为0的强连通分量
2:设现在有a个入度为0的点,b个出度为0的点(缩完点后的点),最合理的加边方法肯定是从出度为0的点向入度为0的点添加有向边,
如果a > b, 添加a条边,所有点的入度都大于0,所有点的出度也大于0,问题解决,答案是a
如果 a <= b,添加a条边,所有点入度大于0,但是还有b-a个点,它们的出度是0,所以还要再加b-a条边,所以答案是b
综合两种情况,答案是max(a,b)

当然如果图原来就是强连通的话,输出就是1 和 0 了


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 110;

struct node
{
	int next;
	int to;
}edge[N * N];

int st[N];
int tot, ord, sccnum, top;
int head[N];
int low[N];
int DFN[N];
int in_deg[N];
int out_deg[N];
int block[N];
bool instack[N];

void addedge(int from, int to)
{
	edge[tot].to = to;
	edge[tot].next = head[from];
	head[from] = tot++;
}

void tarjan (int u)
{
	 DFN[u] = low[u] = ++ord;
	 instack[u] = 1;
	 st[top++] = u;
	 for (int i = head[u]; ~i; i = edge[i].next)
	 {
	 	 int v = edge[i].to;
	 	 if (DFN[v] == -1)
		 {
		 	 tarjan (v);
		 	 if (low[u] > low[v])
			 {
			 	 low[u] = low[v];
			 }
		 }
		 else if (instack[v])
		 {
		 	 low[u] = min(low[u], DFN[v]);
		 }
	 }
	 int v;
	 if (DFN[u] == low[u])
	 {
	 	 sccnum++;
	 	 do
		 {
			v = st[--top];
			block[v] = sccnum;
			instack[v] = 0;
		 }while (v != u);
	 }
}

void init ()
{
	memset (DFN, -1, sizeof(DFN));
	memset (low, 0, sizeof(low));
	memset (head, -1, sizeof(head));
	memset (in_deg, 0, sizeof(in_deg));
	memset (out_deg, 0, sizeof(out_deg));
	memset (instack, 0, sizeof(instack));
	top = 0;
	tot = 0;
	sccnum = 0;
	ord = 0;
}

void solve (int n)
{
	for (int i = 1; i <= n; ++i)
	{
		if (DFN[i] == -1)
		{
			tarjan (i);
		}
	}
	if (sccnum == 1)
	{
		printf("1\n0\n");
		return;
	}
	for (int u = 1; u <= n; ++u)
	{
		for (int j = head[u]; ~j; j = edge[j].next)
		{
			int v = edge[j].to;
			if (block[u] == block[v])
			{
				continue;
			}
			out_deg[block[u]]++;
			in_deg[block[v]]++;
		}
	}
	int a, b;
	a = 0;
	b = 0;
	for (int i = 1; i <= sccnum; ++i)
	{
		if (in_deg[i] == 0)
		{
			a++;
		}
		else if (out_deg[i] == 0)
		{
			b++;
		}
	}
	printf("%d\n", a);
	printf("%d\n", max(a, b));
}

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

B - Network  原题地址

求割点数目,模版题


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 110;

struct node
{
	int next;
	int to;
}edge[N * N];

bool instack[N];
bool cut[N];
int head[N];
int DFN[N];
int low[N];
int cnt, tot, ord, root;

void addedge (int from, int to)
{
	edge[tot].to = to;
	edge[tot].next = head[from];
	head[from] = tot++;
}

void tarjan (int u, int fa)
{
	DFN[u] = low[u] = ++ord;
	instack[u] = 1;
	int cnt = 0;
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == fa)
		{
			continue;
		}
		if (DFN[v] == -1)
		{
			tarjan(v, u);
			cnt++;
			if (low[u] > low[v])
			{
				low[u] = low[v];
			}
			if (root == u && cnt > 1)
			{
				cut[u] = 1;
			}
			else if (u != root && low[v] >= DFN[u])
			{
				cut[u] = 1;
			}
		}
		else if (instack[v])
		{
			low[u] = min(low[u], DFN[v]);
		}
	}
}

void init ()
{
	memset (DFN, -1, sizeof(DFN));
	memset (low, 0, sizeof(low));
	memset (instack, 0, sizeof(instack));
	memset (cut, 0, sizeof(cut));
	memset (head, -1, sizeof(head));
	tot = 0;
	ord = 0;
}

void solve (int n)
{
	root = 1;
	tarjan (1, -1);
	int ans = 0;
	for (int i = 1; i <= n; ++i)
	{
		if (cut[i])
		{
			ans++;
		}
	}
	printf("%d\n", ans);
}

int main()
{
	int n;
	int u, v;
	while (~scanf("%d", &n), n)
	{
		init();
		while (scanf("%d", &u), u)
		{
			while (getchar() != '\n')
			{
				scanf("%d", &v);
				addedge (u, v);
				addedge (v, u);
			}
		}
		solve(n);
	}
	return 0;
}

C - Critical Links

求桥,模版题,这题似乎不用判重边


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 100010;
const int M = (N << 2);

struct node
{
	int next;
	int to;
//	int id;
}edge[M];

struct BRIDGE
{
	int u;
	int v;
}bridge[M];

int head[N];
int DFN[N];
int low[N];
int st[N];
bool instack[N];
int tot, top, ord, sccnum, cnt;

void init ()
{
	memset (head, -1, sizeof(head));
	memset (DFN, -1, sizeof(DFN));
	memset (low, 0, sizeof(low));
	memset (instack, 0, sizeof(instack));
	tot = top = cnt = sccnum = ord = 0;
}

void addedge (int from, int to)
{
	edge[tot].to = to;
//	edge[tot].id = id;
	edge[tot].next = head[from];
	head[from] = tot++;
}

int cmp (BRIDGE a, BRIDGE b)
{
	if (a.u == b.u)
	{
		return a.v < b.v;
	}
	return a.u < b.u;
}

void tarjan (int u, int fa)
{
	DFN[u] = low[u] = ++ord;
	instack[u] = 1;
	st[top++] = u;
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == fa)
		{
			continue;
		}
		if (DFN[v] == -1)
		{
			tarjan (v, u);
			low[u] = min (low[v], low[u]);
			if (low[v] > DFN[u])
			{
				bridge[++cnt].u = u;
				bridge[cnt].v = v;
				if (bridge[cnt].u > bridge[cnt].v)
				{
					swap (bridge[cnt].u, bridge[cnt].v);
				}
			}
		}
		else if (instack[v])
		{
			low[u] = min (low[u], DFN[v]);
		}
	}
	if (low[u] == DFN[u])
	{
		++sccnum;
		int v;
		do
		{
			v = st[--top];
			instack[v] = 0;
		}while (u != v);
	}
}

void solve (int n)
{
	for (int i = 1; i <= n; ++i)
	{
		if (DFN[i] == -1)
		{
			tarjan(i, -1);
		}
	}
	sort (bridge + 1, bridge + cnt + 1, cmp);
	printf("%d critical links\n", cnt);
	for (int i = 1; i <= cnt; ++i)
	{
		printf("%d - %d\n", bridge[i].u - 1, bridge[i].v - 1);
	}
	printf("\n");
}

int main()
{
	int n;
	int u, v;
	int num;
	while (~scanf("%d", &n))
	{
		if (n == 0)
		{
			printf("0 critical links\n\n");
			continue;
		}
		init();
		for (int i = 1; i <= n; ++i)
		{
			scanf("%d", &u);
			++u;
			getchar();
			getchar();
			scanf("%d", &num);
			getchar();
			while (num--)
			{
				scanf("%d", &v);
				++v;
				addedge (u, v);
			}
		}
		solve (n);
	}
	return 0;
}

D - Network  原题地址
敢不敢不叫 Network了

询问每次添加一条边以后剩下的桥的数目
先一次tarjan缩点,得到一颗树,每次 加边,两个端点到它们的lca之间的边都不再是桥,所以每一次我们都可以通过暴力求出lca,然后统计出少了多少条桥,但是暴力统计时,会遇到某些边在之前就不是桥的情况,我们用并查集来跳过这些边(每一次加边就把lca路径上的点都合并到一个集合里去,这里根用最上面的点,到时如果遇到这种点,直接可以跳到它们的根上去)


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;

const int N = 110010;
const int M = 210010;

int head[N];
int head2[N];
int DFN[N];
int low[N];
int deep[N];
int father[N];
int block[N];
int pre[N];
int st[N];
bool instack[N];
int tot, tot2, top, ord, sccnum, icase;

struct node
{
	int next;
	int to;
	int id;
}edge[M << 2], edge2[M << 2];

void init ()
{
	memset (DFN, -1, sizeof(DFN));
	memset (head, -1, sizeof(head));
	memset (head2, -1, sizeof(head2));
	memset (low, 0, sizeof(low));
	memset (instack, 0, sizeof(instack));
	tot = tot2 = ord = sccnum = top = 0;
}

void addedge (int from, int to, int id)
{
	edge[tot].to = to;
	edge[tot].id = id;
	edge[tot].next = head[from];
	head[from] = tot++;
}

void addedge2 (int from, int to)
{
	edge2[tot2].to = to;
	edge2[tot2].next = head2[from];
	head2[from ] = tot2++;
}

void dfs (int u, int fa, int d)
{
	pre[u] = fa;
	deep[u] = d;
	for (int i = head2[u]; ~i; i = edge2[i].next)
	{
		int v = edge2[i].to;
		if (v == fa)
		{
			continue;
		}
		dfs (v, u, d + 1);
	}
}

int find (int x)
{
	if (x == father[x])
	{
		return x;
	}
	return father[x] = find(father[x]);
}

void tarjan (int u, int x)
{
	DFN[u] = low[u] = ++ord;
	instack[u] = 1;
	st[top++] = u;
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (edge[i].id == x)
		{
			continue;
		}
		if (DFN[v] == -1)
		{
			tarjan (v, edge[i].id);
			low[u] = min(low[u], low[v]);
		}
		else if (instack[v])
		{
			low[u] = min(low[u], DFN[v]);
		}
	}
	int v;
	if (DFN[u] == low[u])
	{
		++sccnum;
		do
		{
			v = st[--top];
			instack[v] = 0;
			block[v] = sccnum;
		}while (v != u);
	}
}

int LCA (int a, int b)
{
	while (a != b)
	{
		if (deep[a] > deep[b])
		{
			a = pre[a];
		}
		else if (deep[a] < deep[b])
		{
			b = pre[b];
		}
		else
		{
			a = pre[a];
			b = pre[b];
		}
		a = find(a);
		b = find(b);
	}
	return a;
}

void solve (int n)
{
	tarjan (1, -1);
	for (int u = 1; u <= n; ++u)
	{
		for (int i = head[u]; ~i; i = edge[i].next)
		{
			int v = edge[i].to;
			if (block[u] == block[v])
			{
				continue;
			}
			addedge2 (block[u], block[v]);
		}
	}
	for (int i = 1; i <= sccnum; ++i)
	{
		father[i] = i;
	}
	int cnt = sccnum - 1;
	dfs(1, -1, 0);
	int q, a, b, lca;
	scanf("%d", &q);
	printf("Case %d:\n", icase++);
	while (q--)
	{
		scanf("%d%d", &a, &b);
		a = block[a];
		b = block[b];
		if (a == b)
		{
			printf("%d\n", cnt);
			continue;
		}
		a = find(a);
		b = find(b);
		lca = LCA (a, b);
		int x = 0;
		while (a != lca)
		{
			++x;
			father[a] = lca;
			a = pre[a];
			a = find(a);
		}
		while (b != lca)
		{
			++x;
			father[b] = lca;
			b = pre[b];
			b = find(b);
		}
		cnt -= x;
		printf("%d\n", cnt);
	}	
}

int main()
{
	int n, m;
	int u, v;
	icase = 1;
	while (~scanf("%d%d", &n, &m))
	{
		if (!n && !m)
		{
			break;
		}
		init();
		for (int i = 1; i <= m; ++i)
		{
			scanf("%d%d", &u, &v);
			addedge (u, v, i);
			addedge (v, u, i);
		}
		solve(n);
		printf("\n");
	}
	return 0;
}


E - Redundant Paths 原题地址

求桥的数目,模版题,注意重边的判定

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 5010;
const int M = 10010;

struct node
{
	int next;
	int to;
	int id;
}edge[M << 1];

int head[N];
int DFN[N];
int low[N];
bool instack[N];
int block[N];
int deg[N];
stack  st;
int tot, sccnum, ord;

void addedge (int from, int to, int id)
{
	edge[tot].id = id;
	edge[tot].to = to;
	edge[tot].next = head[from];
	head[from] = tot++;
}

void init ()
{
	memset (DFN, -1, sizeof(DFN));
	memset (low, 0, sizeof(low));
	memset (instack, 0, sizeof(instack));
	memset (deg, 0, sizeof(deg));
	memset (head, -1, sizeof(head));
	while (!st.empty())
	{
		st.pop();
	}
	tot = sccnum = ord = 0;
}

void tarjan (int u, int x)
{
	DFN[u] = low[u] = ++ord;
	instack[u] = 1;
	st.push(u);
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (x == edge[i].id)
		{
			continue;
		}
		if (DFN[v] == -1)
		{
			tarjan (v, edge[i].id);
			low[u] = min(low[u], low[v]);
		}
		else if (instack[v])
		{
			low[u] = min(low[u], DFN[v]);
		}
	}
	int v;
	if (DFN[u] == low[u])
	{
		++sccnum;
		do
		{
			v = st.top();
			st.pop();
			instack[v] = 0;
			block[v] = sccnum;
		}while (v != u);
	}
}

void solve (int n)
{
	for (int i = 1; i <= n; ++i)
	{
		if (DFN[i] == -1)
		{
			tarjan (i, -1);
		}
	}
	for (int u = 1; u <= n; ++u)
	{
		for (int j = head[u]; ~j; j = edge[j].next)
		{
			int v = edge[j].to;
			if (block[u] == block[v])
			{
				continue;
			}
		    ++deg[block[u]];
		    ++deg[block[v]];
		}
	}
	int cnt = 0;
	for (int i = 1; i <= sccnum; ++i)
	{
		if (deg[i] / 2 == 1)
		{
			++cnt;
		}
	}
	printf("%d\n", (cnt + 1) >> 1);
}

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



F - Warm up 原题地址

询问如何加一条边,使得剩下的桥的数目最少,输出数目
我的做法是先tarjan缩点,得到树,然后求出树的直径,把边加在直径的两端,减少的桥肯定是最多的

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 200010;
const int inf = 0x3f3f3f3f;
const int M = 3000010;

struct node
{
    int next;
    int to;
    int id;
}edge[M], edge2[M];

bool vis[N];
bool flag[N];
int dist[N];
int head[N];
int head2[N];
int low[N];
int block[N];
int DFN[N];
bool instack[N];
stackqu;
int br_cnt, n, m, end_p;
int tot, tot2, maxs, index, top, sccnum;

void addedge(int from, int to, int id)
{
    edge[tot].id = id;
    edge[tot].to = to;
    edge[tot].next = head[from];
    head[from] = tot++;
}

void addedge2(int from, int to)
{
    edge2[tot].to = to;
    edge2[tot].next = head2[from];
    head2[from] = tot++;
}

void init()
{
    index = 0;
    sccnum = 0;
    top = 0;
    tot = 0;
    tot2 = 0;
    maxs = 0;
    br_cnt = 0;
    end_p = 0;
    memset ( head, -1, sizeof(head) );
    memset (head2, -1, sizeof(head2) );
    memset ( low, 0, sizeof(low) );
    memset (DFN, 0, sizeof(DFN) );
    memset ( instack, 0, sizeof(instack) );
    memset ( flag, 0, sizeof(flag) );
    memset (vis, 0, sizeof(vis) );

}

void build()
{
    int u, v;
    while (!qu.empty() )
    {
        qu.pop();
    }
    for (int i = 1; i <= m; ++i)
    {
        scanf("%d%d", &u, &v);
        addedge(u, v, i);
        addedge(v, u, i);
    }
}

void tarjan(int u, int fa)  
{  
    qu.push(u);
    instack[u] = 1;
    DFN[u] = low[u] = ++index;  
    for (int i = head[u]; i != -1; i = edge[i].next)  
    {  
        int v = edge[i].to;  
        if (fa == edge[i].id)  
        {
            continue;
        }   
        if ( !instack[v] )  
        {  
            tarjan(v, edge[i].id);  
            low[u] = min(low[u], low[v]);  
            if (DFN[u] < low[v])
            {
                br_cnt++;
            }
        }  
        else 
        {
            low[u] = min(DFN[v], low[u]);
        }  
    }
    if (DFN[u] == low[u])
    {
        sccnum++;
        int v;
        while(1)
        {
            v = qu.top();
            qu.pop();
            instack[v] = 0;
            block[v] = sccnum;
            if(v == u)
            {
                break;
            }
        }
    }
}

void bfs(int s)
{
    queuequ;
    while ( !qu.empty() )
    {
        qu.pop();
    }
    memset ( vis, 0, sizeof(vis) );
    qu.push(s);
    dist[s] = 0;
    vis[s] = 1;
    maxs = 0;
    while ( !qu.empty() )
    {
        int u = qu.front();
        qu.pop();
        for (int i = head2[u]; i != -1; i = edge2[i].next)
        {
            int v = edge2[i].to;
            if (!vis[v])
            {
                vis[v] = 1;
                dist[v] = dist[u] + 1;
                qu.push(v);
                if (maxs < dist[v])
                {
                    maxs = dist[v];
                    end_p = v;
                }
            }
        }
    }
}

void solve()
{
    for (int i = 1; i <= n; ++i)
    {
        if (DFN[i] == 0)
        {
            tarjan(i, -1);
        }
    } 
    for (int u = 1; u <= n; ++u)
    {
        for (int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;
            if (block[u] != block[v])
            {
                addedge2(block[u], block[v]);
            }
        }
    }
    bfs(1);
    bfs(end_p);
    printf("%d\n", br_cnt - maxs);
}

int main()
{
    while (~scanf("%d%d", &n, &m))
    {
        if (!n && !m)
        {
            break;
        }
        init();
        build();
        solve();
    }
    return 0;
}

G - Strongly connected 原题地址

求最多可以加多少边,使得最新的图不是强连通图
最终情况一定是再加一条边,整张图就是强连通的了,那么我们可以把图看成2部分x和y,x和y都是完全图,然后x每个点到y每个点都有边,但是y到x没有边,如果有肯定是强连通了,设x中有a个点,y中有b个点 则 a + b = n
则边数就是 a * (a - 1) + b * (b - 1) + a * b - m,化简得到:n * n - a * b - n - m;
如何让这个值大那就是如何选取x和y的问题了,显然a和b差距越大越好,那么就可以通过tarajan来找出一个包含点最少的强连通分量来当x,其他的强连通分量作为y,这样就很容易了

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 100010;

struct node
{
	int next;
	int to;
}edge[N << 1];

int head[N];
int DFN[N];
int low[N];
int num[N];
int st[N];
bool instack[N];
int block[N];
int in[N];
int out[N];
int tot, top, ord, sccnum;

void addedge (int from, int to)
{
	edge[tot].to = to;
	edge[tot].next = head[from];
	head[from] = tot++;
}

void init ()
{
	memset (num, 0, sizeof(num));
	memset (in, 0, sizeof(in));
	memset (out, 0, sizeof(out));
	memset (head, -1, sizeof(head));
	memset (DFN, -1, sizeof(DFN));
	memset (low, 0, sizeof(low));
	memset (instack, 0, sizeof(instack));
	tot = top = ord = sccnum = 0;
}

void tarjan (int u)
{
	DFN[u] = low[u] = ++ord;
	instack[u] = 1;
	st[top++] = u;
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (DFN[v] == -1)
		{
			tarjan (v);
			low[u] = min (low[u], low[v]);
		}
		else if (instack[v])
		{
			low[u] = min(low[u], DFN[v]);
		}
	}
	if (DFN[u] == low[u])
	{
		int v;
		++sccnum;
		do
		{
			++num[sccnum];
			v = st[--top];
			block[v] = sccnum;
			instack[v] = 0;
		}while (v != u);
	}
}

void solve (int n, int m)
{
	for (int i = 1; i <= n; ++i)
	{
		if (DFN[i] == -1)
		{
			tarjan (i);
		}
	}
	if (sccnum == 1)
	{
		printf("-1\n");
		return;
	}
	for (int u = 1; u <= n; ++u)
	{
		for (int i = head[u]; ~i; i = edge[i].next)
		{
			int v = edge[i].to;
			if (block[u] == block[v])
			{
				continue;
			}
			++out[block[u]];
			++in[block[v]];
		}
	}
	long long ans = 0;
	long long ret = (long long)(n * n - n - m);
	for (int i = 1; i <= sccnum; ++i)
	{
		if (in[i] == 0 || out[i] == 0)
		{
			ans = max(ans, ret - (long long)(num[i] * (n - num[i])));
		}
	}
	printf("%lld\n", ans);
}

int main()
{
	int t;
	int n, m;
	int u, v;
	int icase = 1;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &n, &m);
		init();
		for (int i = 1; i <= m; ++i)
		{
			scanf("%d%d", &u, &v);
			addedge (u, v);
		}
		printf("Case %d: ", icase++);
		solve (n, m);
	}
	return 0;
}


H - Prince and Princess 原题地址

首先做一次最大匹配,设为cnt,那么对于左边,有n-cnt个王子没有匹配,对于右边,有m-cnt个公主没有匹配,所以我们在左边加上m-cnt个虚拟点,这些点喜欢所有公主,右边加上n-cnt个虚拟点,这些点被所有王子喜欢,这样左右两边都是n+m-cnt个点,在求一次最大匹配,这一定是一个完备匹配,对于每一个王子,用他目前匹配的公主,向所有他喜欢的公主连一条有向边,这表示单方面可以替换,所以再对得到的新图求强连通,处在一个强连通分量的公主可以相互替换

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 1100;

int head[N];
int DFN[N];
int low[N];
int block[N];
int match[N];
int match2[N];
bool used[N];
bool instack[N];
bool g[N][N];
stack  st;
vector  vis;
int n, m, tot, sccnum, ord, num_v, num_u;

struct node
{
	int next;
	int to;
}edge[N * N * 5];

void init ()
{
	vis.clear();
	memset (g, 0, sizeof(g));
	memset (match, -1, sizeof(match));
	memset (match2, -1, sizeof(match2));
	memset (used, 0, sizeof(used));
	memset (instack, 0, sizeof(instack));
	memset (DFN, -1, sizeof(DFN));
	memset (low, 0, sizeof(low));
	memset (head, -1, sizeof(head));
	tot = sccnum = ord = 0;
}

void addedge (int from, int to)
{
	edge[tot].to = to;
	edge[tot].next = head[from];
	head[from] = tot++;
}

bool dfs (int u)
{
	for (int i = 1; i <= num_v; ++i)
	{
		if (!g[u][i])
		{
			continue;
		}
		int v = i;
		if (!used[v])
		{
			used[v] = 1;
			if (match[v] == -1 || dfs (match[v]))
			{
				match[v] = u;
				return 1;
			}
		}
	}
	return 0;
}

int hungry ()
{
	int cnt = 0;
	for (int i = 1; i <= num_u; ++i)
	{
		memset (used, 0, sizeof(used));
		if (dfs(i))
		{
			++cnt;
		}
	}
	return cnt;
}

void tarjan (int u)
{
	DFN[u] = low[u] = ++ord;
	instack[u] = 1;
	st.push(u);
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (DFN[v] == -1)
		{
			tarjan (v);
			low[u] = min (low[v], low[u]);
		}
		else if (instack[v])
		{
			low[u] = min (low[u], DFN[v]);
		}
	}
	int v;
	if (low[u] == DFN[u])
	{
		++sccnum;
		do
		{
			v = st.top();
			st.pop();
			instack[v] = 0;
			block[v] = sccnum;
		}while (u != v);
	}
}

void solve ()
{
	num_u = n;
	num_v = m;
	int cnt = hungry();
	num_u = n + m - cnt;
	num_v = num_u;
	for (int i = n + 1; i <= num_u; ++i)
	{
		for (int j = 1; j <= num_v; ++j)
		{
			g[i][j] = 1;
		}
	}
	for (int i = 1; i <= num_u; ++i)
	{
		for (int j = m + 1; j <= num_v; ++j)
		{
			g[i][j] = 1;
		}
	}
//	printf("%d\n", cnt);
	memset (match, -1, sizeof(match));
	cnt = hungry();
//	printf("%d\n", cnt);
	for (int i = 1; i <= num_v; ++i)
	{
		match2[match[i]] = i;
	}
	for (int i = 1; i <= num_u; ++i)
	{
		for (int j = 1; j <= num_v; ++j)
		{
			if (g[i][j] && match2[i] != j)
			{
				addedge (match2[i], j);
			}
		}
	}
	for (int i = 1; i <= num_v; ++i)
	{
		if (DFN[i] == -1)
		{
			tarjan (i);
		}
	}
	for (int i = 1; i <= n; ++i)
	{
		vis.clear();
		for (int j = 1; j <= m; ++j)
		{
			if (g[i][j] && block[j] == block[match2[i]])
			{
				vis.push_back (j);
			}
		}
		int tmp = vis.size();
		printf("%d", tmp);
		for (int j = 0; j < tmp; ++j)
		{
			printf(" %d", vis[j]);
		}
		printf("\n");
	}
}

int main ()
{
	int t;
	int u, v, num;
	int icase = 1;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &n, &m);
		init();
		for (int i = 1; i <= n; ++i)
		{
			scanf("%d", &num);
			while (num--)
			{
				scanf("%d", &v);
				g[i][v] = 1;
			}
		}
		printf("Case #%d:\n", icase++);
		solve();
	}
	return 0;
}


I - Caocao's Bridges  原题地址

此题其实不难,但是很坑
1:如果图不连通,那么不需要派人去
2:如果有一条桥,防卫兵数目为0,那么应该派1个人去
3:重边判定

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 1010;
const int M = N * N;

struct node
{
	int next;
	int to;
	int id;
	int w;
}edge[M << 1];

int head[N];
int DFN[N];
int low[N];
int st[N];
bool instack[N];
int bridge[M];
int tot, cnt, ord, top, sccnum;

void addedge (int from, int to, int id, int w)
{
	edge[tot].w = w;
	edge[tot].to = to;
	edge[tot].id = id;
	edge[tot].next = head[from];
	head[from] = tot++;
}

void init ()
{
	memset (head, -1, sizeof(head));
	memset (DFN, -1, sizeof(DFN));
	memset (low, 0, sizeof(low));
	memset (instack, 0, sizeof(instack));
	tot = cnt = ord = top = sccnum = 0;
}

void tarjan (int u, int x)
{
	DFN[u] = low[u] = ++ord;
	instack[u] = 1;
	st[top++] = u;
	for (int i = head[u]; ~i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (edge[i].id == x)
		{
			continue;
		}
		if (DFN[v] == -1)
		{
			tarjan (v, edge[i].id);
			low[u] = min(low[u], low[v]);
			if (low[v] > DFN[u])
			{
				bridge[++cnt] = edge[i].w;
			}
		}
		else if (instack[v])
		{
			low[u] = min(low[u], DFN[v]);
		}
	}
	int v;
	if (DFN[u] == low[u])
	{
		++sccnum;
		do
		{
			v = st[--top];
			instack[v] = 0;
		}while (u != v);
	}
}

void solve (int n)
{
	int num = 0;
	for (int i = 1; i <= n; ++i)
	{
		if (DFN[i] == -1)
		{
			++num;
			tarjan (i, -1);
		}
		if (num > 1)
		{
			break;
		}
	}
	if (num > 1)
	{
		printf("0\n");
		return;
	}
	if (cnt == 0)
	{
		printf("-1\n");
		return;
	}
	int minx = 0x3f3f3f3f;
	for (int i = 1; i <= cnt; ++i)
	{
		if (minx > bridge[i])
		{
			minx = bridge[i];
		}
	}
	if (minx == 0)
	{
		++minx;
	}
	printf("%d\n", minx);
}

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


你可能感兴趣的:(图论,算法)