zoukankan      html  css  js  c++  java
  • [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;
    }
    
  • 相关阅读:
    [转]Linux里的2>&1究竟是什么
    一段shell脚本分析
    [整理]Linux Crontab命令总结
    random seed()函数
    clear命令新认识
    泛型与发射初探,获取当前代码所在的行
    tomcat集群(转)
    查看本地电脑的端口及对应的使用程序
    信息摘要算法小试牛刀
    Linux非root用户安装jdk和tomcat(转)
  • 原文地址:https://www.cnblogs.com/RioTian/p/13395039.html
Copyright © 2011-2022 走看看