[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 /割点模板题
原题链接:传送门
思路:
- tarjan求割点模板题。
- 割点的条件有:
- 对于根节点:有2棵即以上的子树
- 对于非根节点:\(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;
}
3.Critical Links /桥的模板题
原题链接:传送门
思路:
- Tarjan求桥的模板题。
- 这题有要求按字典序输出,所以可以用vector + pair 来储存。
- 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
原题链接:传送门
思路:
#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;
}