连通图是图论基于联通的一个概念,在ACM中针对图论的考察一部分是也是基于连通图。针对这类问题的解题基本思路就是先求出对应的连通分量(有向图的强连通,无向图的双连通)对图进行简化,然后再结合其他算法计算。
这个题如果能理解题目的话,怎么做就很明显了,能形成一个可以转圈的小群,就相当于一个强连通分量,需要注意的就是这个小群不可以只有一头牛。
#include <set>
#include <map>
#include <list>
#include <stack>
#include <queue>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define IT iterator
#define PB(x) push_back(x)
#define CLR(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vint;
typedef vector<ll> vll;
typedef vector<ull> vull;
typedef set<int> sint;
typedef set<ull> sull;
const int maxn = 10000 + 5;
vint G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
stack<int> S;
int a[maxn];
void init(int n) {
for (int i = 0; i <= n; i++) G[i].clear();
}
void dfs(int u) {
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v);
lowlink[u] = min(lowlink[u],lowlink[v]);
}
else if (!sccno[v]) {
lowlink[u] = min(lowlink[u],pre[v]);
}
}
if (lowlink[u] == pre[u]) {
scc_cnt++;
while (1) {
int x = S.top();
S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
CLR(sccno,0);
CLR(pre,0);
for (int i = 1; i <= n; i++) {
if (!pre[i]) dfs(i);
}
}
int cnt[maxn];
int main() {
int n,m;
//freopen("data.in","r",stdin);
while (cin>>n>>m) {
init(n);
for (int i = 0; i < m; i++) {
int a,b;
scanf("%d%d",&a,&b);
G[a].PB(b);
}
//for (int i = 0; i <= scc_cnt; i++) new_g[i].clear();
find_scc(n);
for (int i = 1; i <= n; i++) {
cnt[sccno[i]]++;
}
int ans = 0;
/*cout<<scc_cnt<<endl;
for (int i = 1; i <= scc_cnt; i++) {
for (int j = 0; j < new_g[i].size(); j++) {
cout<<i<<" "<<new_g[i][j].from<<" "<<new_g[i][j].cost<<endl;
}
}*/
for (int i = 1; i <= scc_cnt; i++) {
if (cnt[i] > 1) ans++;
}
cout<<ans<<endl;
}
}
2. POJ 1236 Network of Schools
最开始对于图的处理同样也是需要用强连通缩点重新构图,然后针对两个任务,第一个很好理解就是入度为0的强连通个数,第二个问题需要小小想一下,欲将整个图变成一个强连通,那么就相当于把现在的图中入度为0和出度为0的点都消灭掉(加边),那么想到这里答案就出来了,就是max{入度为0的点个数,出度为0的点个数}。
#include <set>
#include <map>
#include <list>
#include <stack>
#include <queue>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define IT iterator
#define PB(x) push_back(x)
#define CLR(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vint;
typedef vector<ll> vll;
typedef vector<ull> vull;
typedef set<int> sint;
typedef set<ull> sull;
const int maxn = 10000 + 5;
vint G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
stack<int> S;
int a[maxn];
void init(int n) {
for (int i = 0; i <= n; i++) G[i].clear();
}
void dfs(int u) {
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v);
lowlink[u] = min(lowlink[u],lowlink[v]);
}
else if (!sccno[v]) {
lowlink[u] = min(lowlink[u],pre[v]);
}
}
if (lowlink[u] == pre[u]) {
scc_cnt++;
while (1) {
int x = S.top();
S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
CLR(sccno,0);
CLR(pre,0);
for (int i = 1; i <= n; i++) {
if (!pre[i]) dfs(i);
}
}
int in[maxn];
int out[maxn];
int main() {
int n,m;
//freopen("data.in","r",stdin);
while (cin>>n) {
init(n);
for (int i = 1; i <= n; i++) {
int m;
while (scanf("%d",&m),m) {
G[i].PB(m);
}
}
//for (int i = 0; i <= scc_cnt; i++) new_g[i].clear();
find_scc(n);
CLR(in,0);
CLR(out,0);
int tmpa = 0,tmpb = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < G[i].size(); j++) {
int v = G[i][j];
if (sccno[i] != sccno[v]) {
out[sccno[i]]++;
in[sccno[v]]++;
}
}
}
for (int i = 1; i <= scc_cnt; i++) {
if (!out[i]) tmpb++;
if (!in[i]) tmpa++;
}
cout<<tmpa<<endl<<(scc_cnt == 1 ? 0 : max(tmpb,tmpa))<<endl;
}
}
题目给出一些仰慕关系,需要求的是被所有牛仰慕的牛的个数。由于图相对比较大,所以先强连通缩点,重新构图之后,这样之后就相对简单了,保证图连通的情况下,我们需要保证出度为0的分量只有一个,如果有多个,那么这几个分量之间必然没有倾慕关系。
#include <set>
#include <map>
#include <list>
#include <stack>
#include <queue>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define IT iterator
#define PB(x) push_back(x)
#define CLR(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vint;
typedef vector<ll> vll;
typedef vector<ull> vull;
typedef set<int> sint;
typedef set<ull> sull;
const int maxn = 10000 + 5;
vint G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
stack<int> S;
int a[maxn];
void init(int n) {
for (int i = 0; i <= n; i++) G[i].clear();
}
void dfs(int u) {
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v);
lowlink[u] = min(lowlink[u],lowlink[v]);
}
else if (!sccno[v]) {
lowlink[u] = min(lowlink[u],pre[v]);
}
}
if (lowlink[u] == pre[u]) {
scc_cnt++;
while (1) {
int x = S.top();
S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
CLR(sccno,0);
CLR(pre,0);
for (int i = 1; i <= n; i++) {
if (!pre[i]) dfs(i);
}
}
int out[maxn];
int cnt[maxn];
int main() {
int n,m;
//freopen("data.in","r",stdin);
while (cin>>n>>m) {
init(n);
for (int i = 0; i < m; i++) {
int a,b;
scanf("%d%d",&a,&b);
G[a].PB(b);
}
//for (int i = 0; i <= scc_cnt; i++) new_g[i].clear();
find_scc(n);
CLR(out,0);
CLR(cnt,0);
for (int i = 1; i <= n; i++) {
cnt[sccno[i]]++;
for (int j = 0; j < G[i].size(); j++) {
int v = G[i][j];
if (sccno[i] != sccno[v]) {
out[sccno[i]]++;
}
}
}
bool flag = true;
int k = 0;
for (int i = 1; i <= scc_cnt; i++) {
if (k && !out[i]) {
flag = false;
break;
}
if (!out[i]) k = i;
}
if (flag) cout<<cnt[k]<<endl;
else cout<<0<<endl;
}
}
4. HDU 3861 The King’s Problem
题目读完之后发现应该是最小路径覆盖,但是两个问题:数据范围和图有环。先强连通缩点,重新构图,这样图就变成了DAG,在应用Hungary算法就可以求出答案了。
#include <set>
#include <map>
#include <list>
#include <stack>
#include <queue>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define IT iterator
#define PB(x) push_back(x)
#define CLR(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vint;
typedef vector<ll> vll;
typedef vector<ull> vull;
typedef set<int> sint;
typedef set<ull> sull;
const int maxn = 50000 + 5;
vint G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
stack<int> S;
int a[maxn];
void init(int n) {
for (int i = 0; i <= n; i++) G[i].clear();
}
void dfs(int u) {
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v);
lowlink[u] = min(lowlink[u],lowlink[v]);
}
else if (!sccno[v]) {
lowlink[u] = min(lowlink[u],pre[v]);
}
}
if (lowlink[u] == pre[u]) {
scc_cnt++;
while (1) {
int x = S.top();
S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
CLR(sccno,0);
CLR(pre,0);
for (int i = 1; i <= n; i++) {
if (!pre[i]) dfs(i);
}
}
vint new_g[maxn];
int match[maxn];
bool vis[maxn];
bool findpath(int u) {
for (int i = 0; i < new_g[u].size(); i++) {
int v = new_g[u][i];
if (!vis[v]) {
vis[v] = 1;
if (match[v] == -1 || findpath(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int hungary() {
int ans = 0;
CLR(match,-1);
for (int i = 1; i <= scc_cnt; i++) {
CLR(vis,0);
if (findpath(i)) ans++;
}
return ans;
}
int main() {
//freopen("data.in","r",stdin);
int n,m;
int T;
cin>>T;
while (T--) {
cin>>n>>m;
init(n);
for (int i = 0; i < m; i++) {
int a,b;
scanf("%d%d",&a,&b);
G[a].PB(b);
}
//for (int i = 0; i <= scc_cnt; i++) new_g[i].clear();
find_scc(n);
for (int i = 0; i <= scc_cnt; i++) new_g[i].clear();
for (int i = 1; i <= n; i++) {
for (int j = 0; j < G[i].size(); j++) {
int v = G[i][j];
if (sccno[i] != sccno[v]) {
new_g[sccno[i]].PB(sccno[v]);
}
}
}
cout<<scc_cnt - hungary()<<endl;
}
}
开始还是老套路,先强连通缩点,需要注意的话这个题重新构图需要反向建边,然后对每个入度0的点DFS,求最大值即可。
#include <set>
#include <map>
#include <list>
#include <stack>
#include <queue>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define IT iterator
#define PB(x) push_back(x)
#define CLR(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vint;
typedef vector<ll> vll;
typedef vector<ull> vull;
typedef set<int> sint;
typedef set<ull> sull;
const int maxn = 5000 + 5;
vint G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
stack<int> S;
int a[maxn];
void init(int n) {
for (int i = 0; i <= n; i++) G[i].clear();
}
void dfs(int u) {
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!pre[v]) {
dfs(v);
lowlink[u] = min(lowlink[u],lowlink[v]);
}
else if (!sccno[v]) {
lowlink[u] = min(lowlink[u],pre[v]);
}
}
if (lowlink[u] == pre[u]) {
scc_cnt++;
while (1) {
int x = S.top();
S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
CLR(sccno,0);
CLR(pre,0);
for (int i = 0; i < n; i++) {
if (!pre[i]) dfs(i);
}
}
vint new_g[maxn];
int rec[maxn];
int out[maxn];
int lab[maxn];
bool vis[maxn];
int cnt;
int dp(int u) {
vis[u] = true;
cnt += lab[u];
for (int i = 0; i < new_g[u].size(); i++) {
int v = new_g[u][i];
if (!vis[v]) {
dp(v);
}
}
return cnt;
}
int main() {
//freopen("data.in","r",stdin);
int n,m;
int T;
cin>>T;
for (int t = 1; t <= T; t++) {
cin>>n>>m;
init(n);
for (int i = 0; i < m; i++) {
int a,b;
scanf("%d%d",&a,&b);
G[a].PB(b);
}
find_scc(n);
CLR(out,0);
CLR(lab,0);
for (int i = 0; i <= scc_cnt; i++) new_g[i].clear();
for (int i = 0; i < n; i++) {
lab[sccno[i]]++;
for (int j = 0; j < G[i].size(); j++) {
int v = G[i][j];
if (sccno[i] != sccno[v]) {
new_g[sccno[v]].PB(sccno[i]);
out[sccno[i]]++;
}
}
}
CLR(rec,0);
int ans = 0;
for (int i = 1; i <= scc_cnt; i++) {
if (!out[i]) {
CLR(vis,0);
cnt = 0;
rec[i] = dp(i);
ans = max(ans,rec[i]);
}
}
/*for (int i = 0; i < n; i++) {
cout<<sccno[i]<<" ";
}
cout<<endl;
for (int i = 1; i <= scc_cnt; i++) {
cout<<rec[i][0]<<" "<<rec[i][1]<<endl;
}*/
printf("Case %d: %d\n",t,ans - 1);
bool flag = false;
for (int i = 0; i < n; i++) {
if (rec[sccno[i]] == ans) {
if (flag) printf(" %d",i);
else {
printf("%d",i);
flag = true;
}
}
}
printf("\n");
}
}
题目很好理解,给出了无向图,断开一条边使得分成的两个部分人数差最小。开始先用tarjan求一下边双联通,确定这个图当中桥有哪些,答案就是这些桥中的一个。然后对于缩完点的图,应该是一棵树,这样的话就可以利用DFS的性质,对这一棵树DFS一遍,就可以求出人数差了。
#include <set>
#include <map>
#include <list>
#include <stack>
#include <queue>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define IT iterator
#define PB(x) push_back(x)
#define CLR(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vint;
typedef vector<ll> vll;
typedef vector<ull> vull;
typedef set<int> sint;
typedef set<ull> sull;
const int maxn = 20000 + 5;
struct Edge {
int to,next;
bool cut;
bool cong;
};
stack<int> S;
const int maxm = 40000 + 5;
Edge edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],belong[maxn];
int Index;
int block;
bool Instack[maxn];
int bridge;
int peo[maxn];
int tpe[maxn];
void addedge(int u,int v) {
edge[tot].to = v;
edge[tot].next = head[u];
edge[tot].cut = false;
edge[tot].cong = false;
head[u] = tot++;
}
void tarjan(int u,int pre,bool ff) {
int v;
low[u] = dfn[u] = ++Index;
S.push(u);
Instack[u] = true;
for (int i = head[u]; i != -1; i = edge[i].next) {
v = edge[i].to;
if (v == pre && (!ff)) continue;
if (!dfn[v]) {
tarjan(v,u,edge[i].cong);
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 = S.top();
S.pop();
Instack[v] = false;
belong[v] = block;
}while (v != u);
}
}
void init() {
tot = 0;
CLR(head,-1);
while (!S.empty()) S.pop();
CLR(tpe,0);
}
int sum = 0;
map<ll,int> edgelab;
vint g[maxn];
int minans;
int dfs(int u,int pre) {
int ans = tpe[u];
for (int i = 0; i < g[u].size(); i++) {
if (g[u][i] == pre) continue;
ans += dfs(g[u][i],u);
}
//cout<<ans<<endl;
minans = min(minans,abs(sum - 2 * ans));
return ans;
}
int main() {
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
int n,m;
while (cin>>n>>m) {
init();
sum = 0;
edgelab.clear();
for (int i = 1; i <= n; i++) {
scanf("%d",peo+i);
sum += peo[i];
}
for (int i = 0; i < m; i++) {
int a,b;
scanf("%d%d",&a,&b);
a++;b++;
if (a < b) swap(a,b);
ll tmp = a * 100000 + b;
if (edgelab.find(tmp) != edgelab.end()) {
int lab = edgelab[tmp];
edge[lab].cong = true;
edge[lab^1].cong = true;
}
else {
edgelab[tmp] = tot;
addedge(a,b);
addedge(b,a);
}
}
CLR(dfn,0);
CLR(Instack,false);
Index = block = 0;
tarjan(1,0,false);
if (block == 1) {
cout<<"impossible"<<endl;
continue;
}
for (int i = 0; i <= block; i++) g[i].clear();
minans = 0x3f3f3f3f;
for (int i = 1; i <= n; i++) {
for (int j = head[i]; j != -1; j = edge[j].next) {
if (edge[j].cut) {
g[belong[i]].PB(belong[edge[j].to]);
}
}
}
//for (int i = 1; i <= n; i++) cout<<belong[i]<<endl;
for (int i = 1; i <= n; i++) tpe[belong[i]] += peo[i];
dfs(1,0);
cout<<minans<<endl;
}
}
7. HDU 3849 By Recognizing These Guys, We Find Social Networks Useful
先对顶点进行哈希,然后找到图中的桥,输出出来就行了。需要注意的是,如果图不是连通图的,是不存在桥的概念的,所以缩点之后需要判一下图连通性。
#include <set>
#include <map>
#include <list>
#include <stack>
#include <queue>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define IT iterator
#define PB(x) push_back(x)
#define CLR(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vint;
typedef vector<ll> vll;
typedef vector<ull> vull;
typedef set<int> sint;
typedef set<ull> sull;
struct Edge {
int to,next;
bool cut;//是否是桥
bool cong;//判断重边
};
const int maxn = 20000 + 5;
const int maxm = 200000 + 5;
Edge edge[maxm];
int head[maxn],tot;
int low[maxn],dfn[maxn],Stack[maxn],belong[maxn];
int Index,top;
int block;
bool Instack[maxn];
int bridge;
int n,m;
map<string,int> has;
map<int,string> fas;
void addedge(int u,int v,int tt) {
edge[tot].to = v;
edge[tot].next = head[u];
edge[tot].cut = false;
edge[tot].cong = tt;
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);
}
}
int fa[maxn];
void init() {
tot = 0;
for (int i = 0; i < maxn; i++) fa[i] = i;
has.clear();
fas.clear();
CLR(head,-1);
}
int getf(int x) {
if (fa[x] == x) return x;
else return fa[x] = getf(fa[x]);
}
void unit(int x,int y) {
x = getf(x);
y = getf(y);
fa[x] = y;
}
int main() {
int T;
cin>>T;
while (T--) {
scanf("%d%d",&n,&m);
init();
int cnt = 1;
for (int i = 0; i < m; i++) {
char s1[20],s2[20];
scanf("%s%s",s1,s2);
string tmpa(s1);
string tmpb(s2);
int a,b;
if (has.count(tmpa)) a = has[tmpa];
else {
has[tmpa] = a = cnt++;
fas[a] = tmpa;
}
if (has.count(tmpb)) b = has[tmpb];
else {
has[tmpb] = b = cnt++;
fas[b] = tmpb;
}
unit(a,b);
addedge(a,b,true);
addedge(b,a,false);
}
CLR(dfn,0);
CLR(Instack,false);
Index = top = block = 0;
tarjan(1,0);
int tmp = 0;
for (int i = 1; i <= n; i++) {
if (fa[i] == i) tmp++;
}
if (tmp == 1) {
printf("%d\n",block-1);
for (int i = 1; i <= n; i++) {
for (int j = head[i]; j != -1; j = edge[j].next) {
if (edge[j].cut && edge[j].cong) {
printf("%s %s\n",fas[i].c_str(),fas[edge[j].to].c_str());
}
}
}
}
else printf("0\n");
}
}