[kuangbin]专题九 连通图 题解+总结

kuangbin专题链接:https://vjudge.net/article/752

kuangbin专题十二 基础DP1 题解+总结:https://www.cnblogs.com/RioTian/p/13110438.html

kuangbin专题六 最小生成树 题解+总结:https://www.cnblogs.com/RioTian/p/13380764.html

连通图算法介绍

总结

目录:

1.Network of Schools /强连通分量+缩点

原题链接:传送门

思路:

tarjan求强连通分量,然后缩点,查看有几个强连通图,查看缩点后的DAG入度为0和出度为0的多少,选取其中最大值。

新的一条线为入度为0的连到出度为0,所以取max 。

#include<iostream>
#include<stack>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define ms(a,b) memset(a,b,sizeof a)
typedef long long ll;
const int maxn = 5e5 + 5;
ll head[maxn],dfn[maxn], low[maxn];
bool book[maxn];// dfn 时间戳 low回溯 book 是否在栈中
ll ans[maxn]; // ans为强连通分量里面的点数
ll out[maxn], in[maxn]; // out 出度 in 入度
ll col[maxn], s[maxn];// s栈  col强连通
ll k, ti, top, cnt, n, m;
struct node {
    ll v, next;
}e[maxn << 1];
void add(ll u, ll v) {
    e[++k].v = v; e[k].next = head[u];
    head[u] = k;
}
void tarjan(ll x) {
    dfn[x] = low[x] = ++ti, book[x] = 1, s[++top] = x;
    for (int i = head[x]; i; i = e[i].next) {
        ll u = e[i].v;
        if (!dfn[u]) tarjan(u), low[x] = min(low[x], low[u]);
        else if (book[u]) low[x] = min(low[x], dfn[u]);
    }
    if (low[x] == dfn[x]) {
        ++cnt; ll y;
        do {
            y = s[top--], book[y] = 0;
            col[y] = cnt;
            ans[cnt]++;
        } while (x != y);
    }
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        ll v; while (cin >> v && v)add(i, v);
    }
    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) tarjan(i);
    }
    for(int i = 1;i<=n;++i)
        for(int j=head[i];j;j = e[j].next)
            if (col[e[j].v] != col[i])
                ++out[col[i]], ++in[col[e[j].v]];
    ll res1 = 0, res2 = 0;
    for (int i = 1; i <= cnt; i++) {
        if (!out[i]) res1++;
        if (!in[i])  res2++;
    }
    if (cnt == 1) cout << 1 << endl << 0 << endl;
    else cout << res2 << endl << max(res1, res2) << endl;
}

Kosaraju 算法

#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cstdio>
 
using namespace std;
#define MAX_V 100 + 10
#define ms(a,b) memset(a,b,sizeof a)
 
int V;                      // 顶点数
vector<int> G[MAX_V];       // 图的邻接表表示
vector<int> rG[MAX_V];      // 反向图
vector<int> vs;             // 后序遍历顺序的顶点列表
bool book[MAX_V];           // 访问标记
int cmp[MAX_V];             // 所属强连通分量的拓补序
int in[MAX_V], out[MAX_V];  // 入度、出度
 
void add_Edge(int from, int to) {
	G[from].push_back(to);
	rG[to].push_back(from);
}
 
void dfs(const int &v) {
	book[v] = true;
	for (int i = 0; i < G[v].size(); ++i) {
		if (!book[G[v][i]])
			dfs(G[v][i]);
	}
	vs.push_back(v);
}
 
void rdfs(const int& v, const int& k) {
	book[v] = true;
	cmp[v] = k;
	for (int i = 0; i < rG[v].size(); ++i) {
		if (!book[rG[v][i]])
			rdfs(rG[v][i], k);
	}
}
 
int scc() {
	ms(book, false); vs.clear();
	for (int v = 0; v < V; ++v) {
		if (!book[v])
			dfs(v);
	}
	ms(book, false);
	int k = 0;
	for (int i = vs.size() - 1; i >= 0; --i) {
		if (!book[vs[i]])
			rdfs(vs[i], k++);
	}
	return k;
}
 
int main() {
	//freopen("in.txt","r",stdin);
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	//尝试过快读,莫名比scanf和关闭流同步还速度慢
	cin >> V;
	for (int u = 0, v; u < V; ++u) {
		while (cin>>v  && v)
			add_Edge(u, --v);
	}
 
	int n = scc();
	// 特殊情况
	if (n == 1)return cout << 1 << endl << 0, 0;
 
	for (int u = 0; u < V; ++u)
		for (int i = 0; i < G[u].size(); ++i) {
			int v = G[u][i];
			if (cmp[u] != cmp[v])	// 强连通分量算一个点
				++out[cmp[u]], ++in[cmp[v]];
		}
 
	int zero_in = 0, zero_out = 0;
	for (int i = 0; i < n; ++i){
		if (in[i] == 0) ++zero_in;
		if (out[i] == 0)++zero_out;
	}
	cout << zero_in << endl << max(zero_in, zero_out) << endl;
}

2.Network /割点模板题

原题链接:传送门

思路:

  1. tarjan求割点模板题。
  2. 割点的条件有:
  3. 对于根节点:有2棵即以上的子树
  4. 对于非根节点:\(low[v]>=dfn[u]\)
#include<bits/stdc++.h>
using namespace std;
#define sc(n) scanf("%c",&n)
#define sd(n) scanf("%d",&n)
#define pd(n) printf("%d\n", (n))
#define sdd(n,m) scanf("%d %d",&n,&m)
#define sddd(n,m,z) scanf("%d %d %d",&n,&m,&z)
#define pdd(n,m) printf("%d %d\n",n, m)
#define ms(a,b) memset(a,b,sizeof(a))
#define mod(x) ((x)%MOD)
#define lowbit(x) (x & (-x))

typedef long long ll;
typedef pair<int, int> PII;
typedef vector<int> VI;
typedef vector<string> VS;
const int eps = 1e-6;
const int MOD = 10000007;
const int inf = 0x3f3f3f3f;
const int maxn = 5e5 + 5;

ll head[maxn], dfn[maxn], low[maxn];
bool book[maxn];
ll n, m, id, cnt, tot, k;
struct node {
	ll v, next;
}e[maxn * 2];

void init() {
	memset(book, 0, sizeof(book));
	memset(head, 0, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	id = cnt = tot = k = 0;
}

void add(ll u, ll v) {
	e[++k].v = v;
	e[k].next = head[u];
	head[u] = k;
}

void tarjan(ll u, ll f) {
	dfn[u] = low[u] = ++id;
	int child = 0;
	for (int i = head[u]; i; i = e[i].next) {
		ll v = e[i].v;
		if (!dfn[v]) {
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u] && u != f) book[u] = 1;
			if (u == f) child++;
		}
		low[u] = min(low[u], dfn[v]);
	}
	if (child >= 2 && u == f) book[u] = 1; //如果是根节点,并且有一个以上的子节点
}

int main() {
	//freopen("in.txt", "r", stdin);
	//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int n, u, v;
	char c;
	while (scanf("%d", &n), n) {
		init();
		while (scanf("%d", &u), u) {
			while (scanf("%d%c", &v, &c)) {
				add(u, v), add(v, u);
				if (c == '\n') break;
			}
		}
		for (int i = 1; i <= n; i++)
			if (!dfn[i])
				tarjan(i, i);
		for (int i = 1; i <= n; i++)
			if (book[i])
				tot++;
		cout << tot << endl;
	}
	return 0;
}

原题链接:传送门

思路:

  1. Tarjan求桥的模板题。
  2. 这题有要求按字典序输出,所以可以用vector + pair 来储存。
  3. pair的排序是先排序first后排序second。
#include<bits/stdc++.h>
using namespace std;
#define sc(n) scanf("%c",&n)
#define sd(n) scanf("%d",&n)
#define pd(n) printf("%d\n", (n))
#define sdd(n,m) scanf("%d %d",&n,&m)
#define sddd(n,m,z) scanf("%d %d %d",&n,&m,&z)
#define pdd(n,m) printf("%d %d\n",n, m)
#define ms(a,b) memset(a,b,sizeof(a))
#define mod(x) ((x)%MOD)
#define lowbit(x) (x & (-x))

typedef long long ll;
typedef pair<int, int> PII;
typedef vector<int> VI;
typedef vector<string> VS;
const int eps = 1e-6;
const int MOD = 10000007;
const int inf = 0x3f3f3f3f;
const int maxn = 5e5 + 5;

int N, head[maxn], ecnt;
struct node {
    int u, v;
    int next;
}es[maxn * 10];
void addEdge(int u, int v) {
    es[ecnt].v = v;
    es[ecnt].next = head[u];
    head[u] = ecnt++;
}
int low[maxn], dfn[maxn];
int par[maxn], indx;

typedef pair<int, int> P;
vector<P> ans;
bool cmp(node a, node b) {
    return a.u < b.u;
}

void Tarjan(int u, int pa) {
    par[u] = pa;
    dfn[u] = low[u] = ++indx;
    bool flag = false;
    for (int i = head[u]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (v == pa && !flag) { //判重边
            flag = true;
            continue;
        }
        if (!dfn[v]) {
            Tarjan(v, u);
            low[u] = min(low[u], low[v]);
        }
        else if (pa != v)
            low[u] = min(low[u], dfn[v]);
    }
}
void init() {
    ms(es, 0); ms(low, 0);
    ms(dfn, 0); ms(par, 0);
    ms(head, -1); ans.clear();
    indx = 0, ecnt = 0;
}

int main()
{
    int u, tn, v;
    while (scanf("%d", &N) != EOF) {
        init();
        for (int i = 1; i <= N; ++i) {
            scanf("%d (%d)", &u, &tn);
            while (tn--) {
                scanf(" %d", &v);
                if (v <= u) continue; //减少重边
                addEdge(u, v);
                addEdge(v, u);
            }
        }

        for (int i = 0; i < N; ++i)
            if (!dfn[i])
                Tarjan(i, -1);

        for (int v = 0; v < N; ++v) {
            int u = par[v];
            if (u != -1 && dfn[u] < low[v]) {
                if (v < u) ans.push_back(P(v, u)); //小的在前
                else ans.push_back(P(u, v));
            }
        }

        sort(ans.begin(), ans.end());
        printf("%d critical links\n", ans.size());
        for (int i = 0; i < (int)ans.size(); ++i)
            printf("%d - %d\n", ans[i].first, ans[i].second);
        printf("\n");
    }

    return 0;
}

4.Network /桥+LCA

原题链接:传送门

思路:

image-20200806133412200

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5, M = 6e5 + 5;
struct E{int v, next;} e[M];
struct Node {int fa, j;} f[N];//保存父亲 和通往父亲的那条边 
int n, m, q, u, v, dcc_cnt, id[N], len, dep[N], h[N], dh[N], num, dfn[N], low[N];
bool brid[M];  
void add(int h[], int u, int v) {e[++len].v = v; e[len].next = h[u]; h[u] = len;}
void tarjan(int u, int in_edge) {
	dfn[u] = low[u] = ++num;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v, j);
			low[u] = min(low[u], low[v]);
			if (dfn[u] < low[v]) brid[j] = brid[j ^ 1] = true;
		} else if ((j ^ 1) != in_edge) low[u] = min(low[u], dfn[v]);
 	}
}
void dfs(int u) {
	id[u] = dcc_cnt;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (id[v] || brid[j]) continue; //若是桥不通过
		dfs(v);
	}
}
void bfs() {
	memset(dep, 0, sizeof(dep));
	dep[1] = 1; f[1].fa = 0;
	queue<int> q; q.push(1);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		for (int j = dh[u]; j; j = e[j].next) {
			int v = e[j].v;
			if (dep[v]) continue;
			dep[v] = dep[u] + 1;
			q.push(v); f[v].fa = u; f[v].j = j; 
			//printf("%d==%d-%d\n", u, f[v].fa, f[v].j);
		}
	}
}
int LCA(int x, int y) {
	int cnt = 0;
	if (dep[y] >= dep[x]) swap(x, y);
	while (dep[x] > dep[y]) {
		if (brid[f[x].j]) cnt++, brid[f[x].j] = brid[f[x].j ^ 1] = false;
		x = f[x].fa;
	}
	if (x == y) return cnt;
	while (x != y) {
		int xj = f[x].j, yj = f[y].j;
		if (brid[xj]) cnt++, brid[xj] = brid[xj ^ 1] = false;
		if (brid[yj]) cnt++, brid[yj] = brid[yj ^ 1] = false;
		x = f[x].fa, y = f[y].fa;
	}
	return cnt;
}
int main() {
	int T = 1;
	while (scanf("%d%d", &n, &m), n) {
		memset(h, 0, sizeof(h)); len = num = 1;
		memset(dh, 0, sizeof(dh));
		memset(brid, false, sizeof(brid));
		memset(dfn, 0, sizeof(dfn));
		memset(id, 0, sizeof(id)); 
		for (int i = 1; i <= m; i++) {
			scanf("%d%d", &u, &v);
			add(h, u, v); add(h, v, u);
		}
		tarjan(1, 0);
		//进行 e-dcc 缩点
		dcc_cnt = 0;
		for (int i = 1; i <= n; i++) {
			if (!id[i]) {
				++dcc_cnt;
				dfs(i);
			} 
		} 
		//建立缩点后的树 
		int ans = 0;
		int t = len;
		for (int j = 2; j <= t; j += 2) {
			u = e[j].v, v = e[j ^ 1].v;
			if (id[u] == id[v]) continue;
			brid[len + 1] = brid[len + 2] = true; //这2条边都是桥 
			add(dh, id[u], id[v]); add(dh, id[v], id[u]); ans++; //有一条边代表有一个桥 
		}
		//以1为根节点遍历所有的节点求出f[] 
		bfs();
		scanf("%d", &q);
		printf("Case %d:\n", T++);
		while (q--) {
			scanf("%d%d", &u, &v);
			if (id[u] != id[v]) ans -= LCA(id[u], id[v]);
			printf("%d\n", ans);
		}
	}
	return 0;
}

另一种写法,利用 并查集

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e5+10;
int head[maxn],to[maxn*4],Next[maxn*4];
int f[maxn],ans,pre[maxn];
int dfn[maxn],low[maxn],n,m,tot,num;
bool flag[maxn*4];
void add(int x,int y)
{
	to[++tot]=y;Next[tot]=head[x];head[x]=tot;
}
int getf(int x)
{
	return f[x]==x?x:f[x]=getf(f[x]);
}
bool mer(int x,int y)
{
	int t1=getf(x);
	int t2=getf(y);
	if(t1!=t2)	f[t2]=t1;
	else		return false;
	return true;
}
void tarjan(int x,int in_edge,int fa)
{
	dfn[x]=low[x]=++num;pre[x]=fa;//cout<<x<<" "<<fa<<endl;
	for(int i=head[x];i;i=Next[i]){
		int y=to[i];
		if(!dfn[y]){
			tarjan(y,i,x);
			low[x]=min(low[x],low[y]);
			if(low[y]>dfn[x])
				ans++;
			else
				mer(x,y);
		}
		else	if(i!=(in_edge^1))
			low[x]=min(low[x],dfn[y]);
	}
}
void lca(int x,int y)
{
	if(dfn[x]<dfn[y])	swap(x,y);
	while(dfn[x]>dfn[y]){
		//cout<<x<<" "<<dfn[x]<<" "<<y<<" "<<dfn[y]<<endl;
		if(mer(pre[x],x))	ans--;
		x=getf(pre[x]);
	}
	while(x!=y){
		if(mer(pre[y],y))	ans--;
		y=getf(pre[y]);
	}
}
int main()
{
	int T=0;
	while(cin>>n>>m){
		if(n==0&&m==0)	break;
		if(T)	printf("\n");
		printf("Case %d:\n",++T);
		tot=1;num=0;
		for(int i=1;i<=n;i++)
			dfn[i]=low[i]=head[i]=0,f[i]=i;
		for(int i=1;i<=m;i++){
			int x,y;
			scanf("%d%d",&x,&y);
			add(x,y);add(y,x);
		}
		ans=0;
		for(int i=1;i<=n;i++)
			if(!dfn[i])	tarjan(i,0,0);
		int q;scanf("%d",&q);
		while(q--){
			int t1,t2;scanf("%d%d",&t1,&t2);
			lca(t1,t2);
			cout<<ans<<endl;
		}
	}
	
}

5.Redundant Paths /双连通分量

原题链接:传送门

思路:

  • 题意:求任意2点之间至少存在2条不同的路径。
  • 首先我们可以对图进行边连通分量缩点, 缩点后图就会变成一颗树, 代表任意2点之间的路径是唯一的。 这时候题目转化为添加最少的边使任意2点的路径至少有2条。
  • 结论: 对有n个入度为1的点的树,至少需要 (n + 1) / 2 的边使其变成一个边连通分量。
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5e3 + 5, M = 2e4 + 5;
struct E { int v, next; } e[M];
int n, m, u, v, len, h[N], id[N], dcc_cnt, stack[N], dfn[N], low[N], num, top, ind[N];
bool bridge[M];
void add(int u, int v) { e[++len].v = v; e[len].next = h[u]; h[u] = len; }
void tarjan(int u, int from) {
	dfn[u] = low[u] = ++num;
	stack[++top] = u;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v, j);
			low[u] = min(low[u], low[v]);
			//判断这条边是否是桥
			if (dfn[u] < low[v]) bridge[j] = bridge[j ^ 1] = true;
			//如果不是反向边的话  
		}
		else if (j != (from ^ 1)) low[u] = min(low[u], dfn[v]);
	}
	if (dfn[u] == low[u]) {
		++dcc_cnt; int v;
		do {
			v = stack[top--];
			id[v] = dcc_cnt;
		} while (u != v);
	}
}
int main() {
	len = 1; //节点编号从2开始 
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", &u, &v);
		add(u, v); add(v, u);
	}
	tarjan(1, 0);
	//判断入度为0的点的个数
	int ans = 0;
	for (int u = 1; u <= n; u++) {
		for (int j = h[u]; j; j = e[j].next) {
			int v = e[j].v;
			if (id[u] == id[v]) continue;
			ind[id[v]]++;
		}
	}
	for (int i = 1; i <= dcc_cnt; i++) if (ind[i] == 1) ans++;
	printf("%d", (ans + 1) / 2);
	return 0;
}

6.Strongly connected /强通块+缩点

原题链接:传送门

思路:

思路图源来自dalao — xiaoxiao

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ms(a,b) memset(a,b,sizeof a)
typedef long long ll;
using namespace std;
const int N = 1e5 + 5, M = 1e5 + 5;
struct E { int v, next; } e[M];
int t, n, m, u, v, len, h[N], minv, scc_cnt, top, num, id[N], scc[N], ind[N], outd[N], dfn[N], low[N], stack[N];
bool in_st[N];
void add(int u, int v) { e[++len].v = v; e[len].next = h[u]; h[u] = len; }
void tarjan(int u) {
	dfn[u] = low[u] = ++num;
	stack[++top] = u; in_st[u] = true;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if (in_st[v]) low[u] = min(low[u], dfn[v]);
	}
	if (dfn[u] == low[u]) {
		int v; scc_cnt++;
		do {
			v = stack[top--]; in_st[v] = false;
			id[v] = scc_cnt; scc[scc_cnt]++;
		} while (u != v);
	}
}
int main() {
	scanf("%d", &t); int cas = 1;
	while (t--) {
		len = num = top = scc_cnt = 0; minv = 1e9;
		ms(h, 0); ms(in_st, 0); ms(dfn, 0);
		ms(ind, 0); ms(outd, 0);
		ms(id, 0); ms(scc, 0);
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= m; i++) {
			scanf("%d%d", &u, &v);
			add(u, v);
		}
		for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
		for (int u = 1; u <= n; u++) {
			for (int j = h[u]; j; j = e[j].next) {
				int v = e[j].v;
				if (id[v] == id[u]) continue;
				ind[id[v]]++, outd[id[u]]++;
			}
		}
		//求出度为0 或者 入度为0的SCC的最少点数 
		for (int i = 1; i <= scc_cnt; i++) {
			if (!ind[i] || !outd[i]) minv = min(minv, scc[i]);
		}
		if (scc_cnt == 1) printf("Case %d: -1\n", cas++);
		else printf("Case %d: %lld\n", cas++, (ll)n * (n - 1) - (ll)m - (ll)minv * (n - minv));
	}
	return 0;
}

7.Caocao’s Bridges /桥

原题链接:传送门

思路:

  • 求价值最小的桥。若价值为0,那么还是得派一个人。
  • 若图不连通,那么不需要派人。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e3 + 5, M = 2 * N * N;
struct E {int v, w, next;} e[M];
int n, m, len, u, v, w, h[N], num, dfn[N], low[N];
bool brid[M];
void add(int u, int v, int w) {e[++len].v = v;  e[len].w = w; e[len].next = h[u]; h[u] = len;}
void tarjan(int u, int in_edge) {
	dfn[u] = low[u] = ++num;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v, j);
			low[u] = min(low[u], low[v]);
			if (dfn[u] < low[v]) brid[j] = brid[j ^ 1] = true;
		} else if ((j ^ 1) != in_edge) low[u] = min(low[u], dfn[v]);
	}
}
int main() {
	while (scanf("%d%d", &n, &m), n) {
		memset(h, 0, sizeof(h)); len = num = 1;
		memset(dfn, 0, sizeof(dfn));
		memset(brid, false, sizeof(brid));
		for (int i = 1; i <= m; i++) {
			scanf("%d%d%d", &u, &v, &w);
			add(u, v, w); add(v, u, w);
		}
		int cnt = 0;
		for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i, 0), cnt++;
		int ans = 1e9;
		for (int j = 2; j <= len; j += 2) {
			if (!brid[j]) continue;
			ans = min(ans, e[j].w);	
		}
		if (cnt == 1) printf("%d\n", ans == 1e9 ? -1 : (ans == 0 ? 1 : ans));
		else printf("%d\n", 0); //如果本身不连通 那么不需要派人 
	}
	return 0;
}

8.Warm up /图论综合好题

原题链接:传送门

思路

  • DCC缩点后建立一棵树, 树中的所有边都是桥。 任意连接一条边使桥的数量最少。 那么就是要找出树的最长路径–树的直径。
  • ans = 桥的数量 - 树的直径

还没专门去了解树的直径

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2e5 + 5, M = 4e6 + 6;
struct E {int v, next;} e[M];
int n, m, u, v, len, ans, dh[N], h[N], dcc_cnt, id[N], num, dfn[N], low[N], d[N];
bool brid[M], vis[N]; 
void add(int h[], int u, int v) {e[++len].v = v; e[len].next = h[u]; h[u] = len;}
void tarjan(int u, int in_edge) {
	dfn[u] = low[u] = ++num;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v, j);
			low[u] = min(low[u], low[v]);
			if (dfn[u] < low[v]) brid[j] = brid[j ^ 1] = true;
		} else if ((j ^ 1) != in_edge) low[u] = min(low[u], dfn[v]);
	} 
}
void dfs(int u) {
	id[u] = dcc_cnt;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (id[v] || brid[j]) continue;
		dfs(v);
	} 
}
void dp(int u) {
	vis[u] = true;
	for (int j = dh[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (vis[v]) continue;
		dp(v);
		ans = max(ans, d[u] + d[v] + 1);
		d[u] = max(d[u], d[v] + 1);
	}
}
int main() {
	while (scanf("%d%d", &n, &m), n) {
		memset(h, 0, sizeof(h)); len = num = 1;
		memset(dh, 0, sizeof(dh));
		memset(id, 0, sizeof(id));
		memset(dfn, 0, sizeof(dfn));
		memset(d, 0, sizeof(d));
		memset(brid, false, sizeof(brid));
		memset(vis, false, sizeof(vis));
		for (int i = 1; i <= m; i++) {
			scanf("%d%d", &u, &v);
			add(h, u, v); add(h, v, u);
		}
		tarjan(1, 0); dcc_cnt = 0; 
		//缩点 建树
		for (int i = 1; i <= n; i++) {
			if (!id[i]) {	
			  	dcc_cnt++;
			 	dfs(i);
			 }
		} 
		int tlen = len;  
		for (int j = 2; j <= tlen; j += 2) { 
			u = id[e[j].v], v = id[e[j ^ 1].v];
			if (u == v) continue;
			add(dh, u, v); add(dh, v, u);  
		} 
		ans = 0;
		dp(1); //求出直径 
		printf("%d\n", dcc_cnt - 1 - ans);
	}	
	return 0;
}

9.Prince and Princess /完美匹配+SCC

原题链接: 传送门

思路:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2005, M = 2 * N * N, base = 500;
struct E {int v, next;} e[M]; //王子的点1~500 公主的点501~1000 虚拟点1001~2000 
int n, m, t, u, v, top, len, h[N], scc_cnt, ans[N], id[N], dfn[N], num, low[N], stack[N];
bool in_st[N], love[N][N], vis[N];
int boy[N], girl[N];//boy是王子匹配的公主 
vector<int> scc[N];
void add(int u, int v) {e[++len].v = v; e[len].next = h[u]; h[u] = len;}
void tarjan(int u) {
	dfn[u] = low[u] = ++num;
	stack[++top] = u; in_st[u] = true;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if (in_st[v]) low[u] = min(low[u], dfn[v]);
	}
	if (dfn[u] == low[u]) {
		int v; scc_cnt++;
		do {
			v = stack[top--]; in_st[v] = false;
			id[v] = scc_cnt; 
			if (v > base && v <= 2 * base) scc[scc_cnt].push_back(v - base); //如果是公主的点就放进 
		} while (u != v);
	}
} 
bool dfs(int u) {
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (vis[v]) continue; vis[v] = true;
		if (!girl[v] || dfs(girl[v])) {
			girl[v] = u; boy[u] = v; 
			return true;
		}
	} 
	return false;
} 
void init() {
	for (int i = 1; i <= scc_cnt; i++) scc[i].clear();
	num = len = top = scc_cnt = 0;
	memset(dfn, 0, sizeof(dfn));
	memset(h, 0, sizeof(h));
	memset(id, 0, sizeof(id));
	memset(boy, 0, sizeof(boy));
	memset(girl, 0, sizeof(girl)); 
	memset(love, false, sizeof(love));
}
int main() {
	int T = 1;
	scanf("%d", &t);
	while (t--) {
		init();
		scanf("%d%d", &n, &m); 
		for (int u = 1; u <= n; u++) {
			int len; scanf("%d", &len); 
			while (len--) {
				scanf("%d", &v);	
				love[u][v] = true;
				add(u, v + base); //王子->公主的边 
			}
		} 
		//求最大匹配
		for (int i = 1; i <= n; i++) {
			memset(vis, false, sizeof(vis));
		 	dfs(i);
		} 
		int k = 0; //虚拟点的数量 
		//对未匹配的王子 公主建立虚拟点
		for (int u = 1; u <= n; u++) {
			if (!boy[u]) {
				++k;
				int v = 2 * base + (k);
				boy[u] = v;
				girl[v] = u;
				//所有的王子->虚拟公主 建边 
				for (int i = 1; i <= n; i++) add(i, v);
			} 
		}
		for (int v = base + 1; v <= base + m; v++) {
			if (!girl[v]) {
				k++; 
				int u = 2 * base + k;
				girl[v] = u; boy[u] = v; 
				for (int i = base + 1; i <= base + m; i++) add(u, i);
			}
		} 
		//最后给匹配了的公主建边
		for (int v = base + 1; v <= base + m; v++) {
			add(v, girl[v]); 
		} 
		//虚拟的公主也要建边 
		for (int v = base + 1; v <= base * 2 + k; v++) add(v, girl[v]); 
		//求SCC
		for (int u = 1; u <= n; u++) if (!dfn[u]) tarjan(u);
		printf("Case #%d:\n", T++);
		for (int u = 1; u <= n; u++) {
			int cnt = 0;
			for (int j = 0; j < scc[id[u]].size(); j++) {
				int v = scc[id[u]][j];
				if (love[u][v]) ans[++cnt] = v;
			} 
			sort(ans + 1, ans + 1 + cnt);
			printf("%d", cnt);
			for (int i = 1; i <= cnt; i++) printf(" %d", ans[i]); printf("\n");
		} 
	} 
	return 0;
}
posted @ 2020-08-08 15:38  RioTian  阅读(446)  评论(0编辑  收藏  举报