1. 程式人生 > >[kuangbin帶你飛]專題九 連通圖 題解報告

[kuangbin帶你飛]專題九 連通圖 題解報告

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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

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 <int> 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 <map>
#include <set>
#include <list>
#include <stack>
#include <vector>
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#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];
stack<int>qu;
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)
{
    queue<int>qu;
    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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

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 <int> st;
vector <int> 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 <map>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

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;
}