[kuangbin帶你飛]專題九 連通圖 題解報告
阿新 • • 發佈:2019-02-01
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 了
B - Network 原題地址
求割點數目,模版題
C - Critical Links
求橋,模版題,這題似乎不用判重邊
D - Network 原題地址
敢不敢不叫 Network了
詢問每次新增一條邊以後剩下的橋的數目
先一次tarjan縮點,得到一顆樹,每次 加邊,兩個端點到它們的lca之間的邊都不再是橋,所以每一次我們都可以通過暴力求出lca,然後統計出少了多少條橋,但是暴力統計時,會遇到某些邊在之前就不是橋的情況,我們用並查集來跳過這些邊(每一次加邊就把lca路徑上的點都合併到一個集合裡去,這裡根用最上面的點,到時如果遇到這種點,直接可以跳到它們的根上去)
E - Redundant Paths 原題地址
求橋的數目,模版題,注意重邊的判定
F - Warm up 原題地址
詢問如何加一條邊,使得剩下的橋的數目最少,輸出數目
我的做法是先tarjan縮點,得到樹,然後求出樹的直徑,把邊加在直徑的兩端,減少的橋肯定是最多的
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,這樣就很容易了
H - Prince and Princess 原題地址
首先做一次最大匹配,設為cnt,那麼對於左邊,有n-cnt個王子沒有匹配,對於右邊,有m-cnt個公主沒有匹配,所以我們在左邊加上m-cnt個虛擬點,這些點喜歡所有公主,右邊加上n-cnt個虛擬點,這些點被所有王子喜歡,這樣左右兩邊都是n+m-cnt個點,在求一次最大匹配,這一定是一個完備匹配,對於每一個王子,用他目前匹配的公主,向所有他喜歡的公主連一條有向邊,這表示單方面可以替換,所以再對得到的新圖求強連通,處在一個強連通分量的公主可以相互替換
I - Caocao's Bridges 原題地址
此題其實不難,但是很坑
1:如果圖不連通,那麼不需要派人去
2:如果有一條橋,防衛兵數目為0,那麼應該派1個人去
3:重邊判定
本題有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;
}