解题报告:【kuangbin带你飞】专题九 连通图

A、POJ 1236 Network of Schools(有向图缩点)

A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B
.
You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school.

Input
The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.

Output
Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.

tarjian缩点,因为一个强连通分块里面可以互相到达,那么可以当成一个点处理。
任务A:
需要求最多在多少学校发送新软件,其实就是求缩点后入度为0的个数(如果入度不为0就可以从其他学校传过来)
任务B:
求入度为0的点数与出度为0的点的较大值。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>

using namespace std;
typedef long long ll;
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;

int n, m;
int dfn[N], low[N];
int head[N], nex[M], ver[M], tot;
int scc_id[N], cnt_scc;
bool ins[N];
int stk[N], top;
vector<int>scc[N];

void add(int x, int y)
{
    
    
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}
int ind;

void tarjan(int x)
{
    
    
    dfn[x] = low[x] = ++ ind;
    stk[++ top] = x ;
    ins[x] = 1;
    for(int i = head[x]; ~i; i = nex[i]){
    
    
        int y = ver[i];
        if(!dfn[y]){
    
    
            tarjan(y);
            low[x] = min(low[x], low[y]);
        }
        else if(ins[y])
            low[x] = min(low[x], dfn[y]);
    }
    if(dfn[x] == low[x]){
    
    
        int y;
        cnt_scc ++ ;
        do{
    
    
            y = stk[top -- ];
            ins[y] = 0;
            scc_id[y] = cnt_scc;
            scc[cnt_scc].push_back(y);
        }while(x != y);
    }
}

int ansi, anso;
int in[N], out[N];

int main()
{
    
    
    scanf("%d", &n);
    memset(head, -1, sizeof head);
    for(int i = 1; i <= n;++ i){
    
    
        int x;
        scanf("%d", &x);
        while(x){
    
    
            add(i, x);
            scanf("%d", &x);
        }
    }
    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 = nex[j]){
    
    
            int k = ver[j];
            if(scc_id[i] != scc_id[k]){
    
    
                out[scc_id[i]] ++ ;
                in[scc_id[k]] ++ ;
            }
        }
    }

    for(int i = 1; i <= cnt_scc; ++ i){
    
    
        if(!out[i])
            anso ++ ;
        if(!in[i])
            ansi ++;
    }

    printf("%d\n", ansi);

    if(cnt_scc == 1){
    
    
        puts("0");
    }
    else {
    
    
        printf("%d\n", max(ansi, anso));
    }
    return 0;
}

B、UVA 315 Network(找割点)

A Telephone Line Company (TLC) is establishing a new telephone cable network. They are connecting
several places numbered by integers from 1 to N. No two places have the same number. The lines
are bidirectional and always connect together two places and in each place the lines end in a telephone
exchange. There is one telephone exchange in each place. From each place it is possible to reach
through lines every other place, however it need not be a direct connection, it can go through several
exchanges. From time to time the power supply fails at a place and then the exchange does not operate.
The officials from TLC realized that in such a case it can happen that besides the fact that the place
with the failure is unreachable, this can also cause that some other places cannot connect to each other.
In such a case we will say the place (where the failure occured) is critical. Now the officials are trying
to write a program for finding the number of all such critical places. Help them.

Input
The input file consists of several blocks of lines. Each block describes one network. In the first line
of each block there is the number of places N < 100. Each of the next at most N lines contains the
number of a place followed by the numbers of some places to which there is a direct line from this place.
These at most N lines completely describe the network, i.e., each direct connection of two places in
the network is contained at least in one row. All numbers in one line are separated by one space. Each
block ends with a line containing just ‘0’. The last block has only one line with N = 0.

Output
The output contains for each block except the last in the input file one line containing the number of
critical places

多组数据读入一个数n,n=0时退出,代表n个点的无向图。下面若干行,每行第一个数为u,u=0时退出,后面若干个数v,代表和u相连的点v,遇到换行退出。对于每组数据,输出图的割顶个数

输入比较麻烦,然后就是找割点的模板了

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;

const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;
int n, m;
int dfn[N], low[N], ind;
int head[N], ver[M], nex[M], tot;
int stk[N], top;
int cut[N], num;
bool bridge[N];
int cnt;
bool ins[N];
vector<int>dcc[N];

void add(int x, int y)
{
    
    
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}
int root;

void tarjan(int x)
{
    
    
    dfn[x] = low[x] = ++ num;
    stk[++ top] = x;
    int flag = 0;
    for(int i = head[x]; ~i; i = nex[i]){
    
    
        int y = ver[i];
        if(!dfn[y]){
    
    
            tarjan(y);
            low[x] = min(low[x], low[y]);
            if(low[y] >= dfn[x]){
    
    
                flag ++ ;
                if(x != root || flag > 1)cut[x] =  true;
            }
        }
        else low[x] = min(low[x], dfn[y]);
    }
}

int main()
{
    
    
    while(~scanf("%d", &n) && n){
    
    
        cnt = tot = num = root = top = 0;
        for(int i = 1; i <= n; ++ i)
        dcc[i].clear();
        memset(head, -1 ,sizeof head);
        memset(cut, 0, sizeof cut);
        memset(dfn, 0, sizeof dfn);
        memset(low, 0, sizeof low);
        int x, y;
        while(~scanf("%d", &x) && x){
    
    
            while(1){
    
    
                char ch = getchar();
                if(ch == '\n')break;

                scanf("%d", &y);
                add(x, y);
                add(y ,x);
            }
        }
        for(int i = 1; i  <= n; ++ i){
    
    
            if(!dfn[i])
                root = i, tarjan(i);
        }

        int ans = 0;

        for(int i = 1; i <= n; ++ i)
            if(cut[i])ans ++ ;

        printf("%d\n", ans);
    }
    return 0;
}

C、UVA 796 Critical Links(桥)

在这里插入图片描述
求桥的模板
就是输入输出有点恶心,可以用格式化scanf输入。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>

using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second

const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;
int n, m;
int dfn[N], low[N], ind;
int head[N], ver[M], nex[M], tot;
int stk[N], top;
int cut[N], num;
bool bridge[N];
int cnt;
bool ins[N];
int bridge_num;
vector<int>dcc[N];

void put1(){
    
     puts("YES");}
void put2(){
    
     puts("NO") ;}
void put3(){
    
     puts("-1") ;}

void add(int x, int y)
{
    
    
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

void init()
{
    
    
    num = tot = bridge_num = cnt = 0;
    memset(head, -1, sizeof head);
    memset(dfn, 0, sizeof dfn);
    memset(low, 0, sizeof low);
    memset(bridge, 0, sizeof bridge);
}

void tarjan(int x, int fa)
{
    
    
    dfn[x] = low[x] = ++ num;
    for(int i = head[x] ;~i; i = nex[i]){
    
    
        int y = ver[i];
        if(!dfn[y]){
    
    
            tarjan(y, x);
            low[x] = min(low[x], low[y]);
            if(dfn[x] < low[y]){
    
    
                bridge[i] = bridge[i ^ 1] = true, bridge_num ++ ;
            }
        }
        else if(fa != y) 
            low[x] = min(low[x], dfn[y]);
    }
}

int main()
{
    
    
    while(~scanf("%d", &n)){
    
    
        init();
        for(int i = 1; i <= n; ++ i){
    
    
            int x, y;
            scanf("%d (%d)", &x, &m);
            for(int j = 1; j <= m; ++ j)
            scanf("%d", &y), add(x, y), add(y, x);
        }
        for(int i = 1; i <= n; ++ i)
            if(!dfn[i])
                tarjan(i, -1);
                
        vector<PII>ans;

        for(int i = 0; i < tot;i += 2){
    
    
            if(bridge[i]){
    
    
                if(ver[i] < ver[i ^ 1])ans.push_back({
    
    ver[i], ver[i ^ 1]});
                else ans.push_back({
    
    ver[i ^ 1], ver[i]});
            }
        }

        sort(ans.begin(), ans.end());
        
        cout << bridge_num << " critical links" << endl;
        for(int i = 0; i < (int)ans.size(); ++ i)
            printf("%d - %d\n", ans[i].x, ans[i].y);
        puts("");
    }
    return 0;
}

D、POJ 3694 Network(Tarjan割边缩点 + LCA + 并查集优化)

【割边缩点】解题报告:POJ - 3694 - Network(Tarjan割边缩点 + LCA + 并查集优化)

//这道题代码虽然比较长,但是每个函数各司其职,井井有条,写起来非常舒服
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 2e5+7;
const int M = 5e5+7;

int head[N],nex[M],ver[M],tot = 1;//原图
int hc[N],nc[M],vc[M],tc = 1;//缩点后建图
int dfn[N],low[N],num;//tarjan专用
int dcc,cnt;//强连通专用
int n,m,t,T;//输入专用
bool bridge[M];//割边专用
int deep[N],f[N][20];//LCA专用
int c[N];//缩点专用
int fa[N];//并查集专用

void add(int x,int y){
    
    
    ver[++tot] = y;nex[tot] = head[x];head[x] = tot;
}

void add_c(int x,int y){
    
    
    vc[++tc] = y;nc[tc] = hc[x];hc[x] = tc;
}

void tarjan(int x,int in_edge){
    
    
    dfn[x] = low[x] = ++num;
    for(int i = head[x];i;i = nex[i]){
    
    
        int y = ver[i];
        if(!dfn[y]){
    
    
            tarjan(y,i);
            low[x] = min(low[x],low[y]);
            if(low[y] > dfn[x])
                bridge[i] = bridge[i ^ 1] = true;//成对变换
        }
        else if(i != (in_edge ^ 1))//i和in_edge都是前向星的指针编号
            low[x] = min(low[x],dfn[y]);
    }
}

void dfs(int x){
    
    
    c[x] = dcc;
    for(int i = head[x];i;i = nex[i]){
    
    
        int y = ver[i];
        if(c[y] || bridge[i])continue;
        dfs(y);
    }
}

queue<int>q;

void bfs(){
    
    //求的是缩点后的图
    deep[1] = 1;//根节点的深度是1
    q.push(1);
    while(q.size()){
    
    
        int x = q.front();
        q.pop();
        for(int i = hc[x];i;i = nc[i]){
    
    
            int y = vc[i];
            if(deep[y])continue;
            deep[y] = deep[x] + 1;
            f[y][0] = x;
            over(j,1,19)
                f[y][j] = f[f[y][j - 1]][j - 1];
            q.push(y);
        }
    }
}

int lca(int x,int y){
    
    
    if(deep[x] > deep[y])swap(x,y);
    lver(i,18,0)
        if(deep[f[y][i]] >= deep[x])
            y = f[y][i];
    if(x == y)return x;
    lver(i,18,0)
    if(f[x][i] != f[y][i])
        x = f[x][i],y = f[y][i];
    return f[x][0];
}

int Get(int x){
    
    
    if(x == fa[x])return x;
    return fa[x] = Get(fa[x]);
}

int main()
{
    
    
    while(scanf("%d%d",&n,&m) != EOF && n){
    
    

        tot = 1;dcc = num = 0;
        over(i,1,n)
            head[i]  = hc[i] = dfn[i] = low[i] = deep[i] = c[i] = 0;
        over(i,1,2 * m + 1)
            bridge[i] = 0;
        over(i,1,m){
    
    
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);add(y,x);
        }
        over(i,1,n)
            if(!dfn[i])
                tarjan(i,0);
        //两个套路都一样
        over(i,1,n)
            if(!c[i])
                ++dcc,dfs(i);
        //缩完点该建图了
        tc = 1;
        over(i,2,tot){
    
    //建图就用到了成对变换
                int x = ver[i ^ 1],y = ver[i];
                if(c[x] == c[y])continue;
                add_c(c[x],c[y]);
                //因为之前是无向图转有向图是双边,这里已经是有向图了只需要建单边即可
        }
        //lca预处理
        bfs();
        //并查集初始化
        over(i,1,dcc)
            fa[i] = i;
        scanf("%d",&t);
        int ans = dcc - 1;//dcc是缩点后的点数,而ans是边数,所以最开始ans,树的边数等于点数 - 1
        printf("Case %d:\n", ++T);
        while(t--){
    
    
            int x,y;
            scanf("%d%d",&x,&y);
            x = c[x],y = c[y];
            int p = lca(x,y);
            x = Get(x);
            while(deep[x] > deep[p]){
    
    
                fa[x] = f[x][0];//先给fa赋值
                ans--;
                x = Get(x);//这样get的时候x也能往上走
            }
            y = Get(y);
            while(deep[y] > deep[p]){
    
    
                fa[y] = f[y][0];
                ans--;
                y = Get(y);
            }
            printf("%d\n",ans);
        }
        cout<<endl;
    }
    return 0;
}


E、POJ 3177 Redundant Paths(结论:变成边双连通分量所需要新建的边数)

In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numbered 1…F) to another field, Bessie and the rest of the herd are forced to cross near the Tree of Rotten Apples. The cows are now tired of often being forced to take a particular path and want to build some new paths so that they will always have a choice of at least two separate routes between any pair of fields. They currently have at least one route between each pair of fields and want to have at least two. Of course, they can only travel on Official Paths when they move from one field to another.
Given a description of the current set of R (F-1 <= R <= 10,000) paths that each connect exactly two different fields, determine the minimum number of new paths (each of which connects exactly two fields) that must be built so that there are at least two separate routes between any pair of fields. Routes are considered separate if they use none of the same paths, even if they visit the same intermediate field along the way.
There might already be more than one paths between the same pair of fields, and you may also build a new path that connects the same fields as some other path.

Input
Line 1: Two space-separated integers: F and R
Lines 2…R+1: Each line contains two space-separated integers which are the fields at the endpoints of some path.

Output
Line 1: A single integer that is the number of new paths that must be built.

无向图
连多少条边使得整个无向图变成任意两个点之间都有两条完全不相同的路径。

  • 结论1:任意两个点之间都有两条完全不相同的路径 = > => => 没有割边 = > => => 边双连通图
  • 结论2: 需要新建的边数: c n t + 1 2 \frac{cnt + 1}{2} 2cnt+1 (下取整)(其中cnt为将所有的边双连通图缩点以后有割桥形成树的叶子节点,也就是度数为1的结点数)

首先我们可以对图进行边连通分量缩点, 缩点后图就会变成一颗树, 代表任意2点之间的路径是唯一的。 这时候题目转化为添加最少的边使树上任意
2点的路径至少有2条。

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;

const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;

int n, m;
int dfn[N], low[N], num;
int head[N], ver[M], nex[M], edge[M], tot;
int dcc_cnt, cnt;
bool bridge[N];
int dcc_id[N];
int d[N];

void init(){
    
    
    tot = num = cnt = dcc_cnt = 0;
    memset(head, -1, sizeof head);
    memset(dfn, 0, sizeof dfn);
    memset(low, 0, sizeof low);
    memset(bridge, 0, sizeof bridge);
}

void add(int x, int y)
{
    
    
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

void tarjan(int x, int in_edge)
{
    
    
    dfn[x] = low[x] = ++ num;
    for(int i = head[x]; ~i; i = nex[i]){
    
    
        int y = ver[i];
        if(!dfn[y]){
    
    
            tarjan(y, i);
            low[x] = min(low[x], low[y]);
            if(dfn[x] < low[y]){
    
    
                bridge[i] = bridge[i ^ 1] = true;
            }
        }
        else if(i != (in_edge ^ 1))
            low[x] = min(low[x], dfn[y]);
    }
}

void dfs(int x)
{
    
    
    dcc_id[x] = dcc_cnt;
    for(int i = head[x] ;~i; i = nex[i]){
    
    
        int y = ver[i];
        if(dcc_id[y] || bridge[i])continue;
        dfs(y);
    }
}

int main()
{
    
    
    while(scanf("%d%d", &n, &m) != EOF){
    
    
        init();
        for(int i = 1; i <= m; ++ i){
    
    
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y), add(y, x);
        }

        tarjan(1, -1);//整个图是连通的

        for(int i = 1; i <= n; ++ i){
    
    
            if(!dcc_id[i]){
    
    
                ++ dcc_cnt;
                dfs(i);
            }
        }

        for(int i = 0; i < tot; i += 2){
    
    
            if(bridge[i]){
    
    
                int x = ver[i], y = ver[i ^ 1];
                d[dcc_id[x]] ++ ;
                d[dcc_id[y]] ++ ;
            }
        }

        ll cnt = 0;
        for(int i = 1; i <= dcc_cnt; ++ i){
    
    
            if(d[i] == 1)
                cnt ++ ;
        }
        printf("%lld\n", (cnt + 1 ) / 2);
    }
    return 0;
}

F、HDU 4612 Warm up(边双连通分量缩点、建树、树的直径)

N planets are connected by M bidirectional channels that allow instant transportation. It’s always possible to travel between any two planets through these channels.
  If we can isolate some planets from others by breaking only one channel , the channel is called a bridge of the transportation system.
People don’t like to be isolated. So they ask what’s the minimal number of bridges they can have if they decide to build a new channel.
  Note that there could be more than one channel between two planets.
Input
  The input contains multiple cases.
  Each case starts with two positive integers N and M , indicating the number of planets and the number of channels.
  (2<=N<=200000, 1<=M<=1000000)
  Next M lines each contains two positive integers A and B, indicating a channel between planet A and B in the system. Planets are numbered by 1…N.
  A line with two integers ‘0’ terminates the input.
Output
  For each case, output the minimal number of bridges after building a new channel in a line.

两个点一旦连边,那么他们两点的那一条唯一路径之间的边就都不再是桥了

所以我们要求连一条边使得桥的数目变到最少,那么我们可以将整个图缩点,建一颗树,每一条边就是一个桥,那么我们要使得桥的数目变得最少,也就是说要删去的桥的数目最多,那么我们直接找到这颗树的直径,直径就意味着整棵树中两个点之间最长的路径,就是能减少的桥的最大个数

#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;
}

G、HDU 4635 Strongly connected(最多可加边数使得仍然非强连通)

HDU 4635 Strongly connected

Give a simple directed graph with N nodes and M edges. Please tell me the maximum number of the edges you can add that the graph is still a simple directed graph. Also, after you add these edges, this graph must NOT be strongly connected.
A simple directed graph is a directed graph having no multiple edges or graph loops.
A strongly connected digraph is a directed graph in which it is possible to reach any node starting from any other node by traversing edges in the direction(s) in which they point.

Input
The first line of date is an integer T, which is the number of the text cases.
Then T cases follow, each case starts of two numbers N and M, 1<=N<=100000, 1<=M<=100000, representing the number of nodes and the number of edges, then M lines follow. Each line contains two integers x and y, means that there is a edge from x to y.

Output
For each case, you should output the maximum number of the edges you can add.
If the original graph is strongly connected, just output -1.

给定一个有向图,求最大可以增加多少条边使得这个图仍然不是强连通图。

在这里插入图片描述

图片来源

  • 最大可以增加多少条边使得这个图仍然不是强连通图

结论: n ∗ ( n − 1 ) − m − m i n ∗ ( n − m i n v ) n * (n - 1) - m - min * (n - minv) n(n1)mmin(nminv)(其中n为点数、m为边数、minv为缩点以后的点中入度和出度至少有一个为0的包含节点个数最少的点)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;

const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;

int n, m;
int head[N], ver[M], nex[M], tot;
int dfn[N], low[N], ind;
int stk[N], top;
int scc_id[N];
int scc_cnt;
bool ins[N];
int scc_num[N];
vector<int>scc[N];
int t;

void add(int x, int y){
    
    
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

void tarjan(int x)
{
    
    
    dfn[x] = low[x] = ++ ind;
    stk[++ top] = x, ins[x] = true;
    for(int i = head[x]; ~i ; i = nex[i]){
    
    
        int y = ver[i];
        if(!dfn[y]){
    
    
            tarjan(y);
            low[x] = min(low[x], low[y]);
        }
        else if(ins[y])
            low[x] = min(low[x], dfn[y]);
    }
    if(dfn[x] == low[x]){
    
    
        int y;
        scc_cnt ++;
        do{
    
    
            y = stk[top -- ];
            ins[y] = false;
            scc_id[y] = scc_cnt;
            scc_num[scc_cnt] ++ ;
            scc[scc_cnt].push_back(y);
        }while(x != y);
    }
}

int in[N], out[N];
int ans;
int cnt;
int main()
{
    
    
    scanf("%d", &t);
    while(t -- ){
    
    
        cnt ++ ;
        tot = 0, ind = 0, scc_cnt = 0;
        memset(dfn, 0, sizeof dfn);
        memset(low, 0, sizeof low);
        memset(head, -1, sizeof head);
        memset(in, 0, sizeof in);
        memset(out, 0, sizeof out);
        memset(scc_id, 0,sizeof scc_id);
        memset(scc_num, 0, sizeof scc_num);

        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; ++ i){
    
    
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y);
        }

        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 = nex[j]){
    
    
                int k = ver[j];
                if(scc_id[i] != scc_id[k]){
    
    
                    out[scc_id[i]] ++ ;
                    in[scc_id[k]] ++ ;
                }
            }
        }

        int minv = INF;
        for(int i = 1; i <= scc_cnt; ++ i){
    
    
            if(!in[i] || !out[i]){
    
    
                minv = min(minv, scc_num[i]);//找到包含点最少并且出度和入度至少有一个为0的点
            }
        }

        if(scc_cnt == 1){
    
    
            printf("Case %d: -1\n", cnt);
        }
        else {
    
    
            printf("Case %d: %lld\n", cnt, 1ll * n * (n - 1) - m - minv * (1ll * n - minv));
        }
    }
    return 0;
}

H、HDU 4685 Prince and Princess(最大匹配、缩点、给王子匹配公主系列)

I、HDU 4738 Caocao’s Bridges(桥、任何位运算一定都要加括号、因为有重边所以用前向星)

Caocao was defeated by Zhuge Liang and Zhou Yu in the battle of Chibi. But he wouldn’t give up. Caocao’s army still was not good at water battles, so he came up with another idea. He built many islands in the Changjiang river, and based on those islands, Caocao’s army could easily attack Zhou Yu’s troop. Caocao also built bridges connecting islands. If all islands were connected by bridges, Caocao’s army could be deployed very conveniently among those islands. Zhou Yu couldn’t stand with that, so he wanted to destroy some Caocao’s bridges so one or more islands would be seperated from other islands. But Zhou Yu had only one bomb which was left by Zhuge Liang, so he could only destroy one bridge. Zhou Yu must send someone carrying the bomb to destroy the bridge. There might be guards on bridges. The soldier number of the bombing team couldn’t be less than the guard number of a bridge, or the mission would fail. Please figure out as least how many soldiers Zhou Yu have to sent to complete the island seperating mission.

Input
There are no more than 12 test cases.
In each test case:
The first line contains two integers, N and M, meaning that there are N islands and M bridges. All the islands are numbered from 1 to N. ( 2 <= N <= 1000, 0 < M <= N2 )
Next M lines describes M bridges. Each line contains three integers U,V and W, meaning that there is a bridge connecting island U and island V, and there are W guards on that bridge. ( U ≠ V and 0 <= W <= 10,000 )
The input ends with N = 0 and M = 0.

Output
For each test case, print the minimum soldier number Zhou Yu had to send to complete the mission. If Zhou Yu couldn’t succeed any way, print -1 instead.

题意:曹操有一个无向图,图中有一些边,周瑜现在要派一些人去炸一条边,使曹操的无向图分为不同部分,曹操的每条边上都有守卫,周瑜派的人不能少于边上守卫数,问最少要派多少人。

  • 注意重边处理, 要用链式前向星。
  • 守卫为0时,也需要派一个人;
  • 如果原本图不连通(tarjan多次),就不需要派人,答案就是0。
  • 答案直接输出所有桥中的最小边权。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;

int n, m;
int bridge[N];
int dfn[N], low[N], num;
int head[N], ver[M], nex[M], edge[M], tot;
int ans;
int dcc_cnt;
int min_bridge;

void init()
{
    
    
    num = tot = dcc_cnt = 0;
    memset(head, -1, sizeof head);
    memset(dfn, 0, sizeof dfn);
    memset(low, 0, sizeof low);
    min_bridge = INF;
}

void add(int x, int y, int z)
{
    
    
    ver[tot] = y;
    edge[tot] = z;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

void tarjan(int x, int in_edge)
{
    
    
    dfn[x] = low[x] = ++ num;
    for(int i = head[x] ;~i; i = nex[i]){
    
    
        int y = ver[i], z = edge[i];
        if(!dfn[y]){
    
    
            tarjan(y, i);
            low[x] = min(low[x], low[y]);
            if(dfn[x] < low[y]){
    
    
                bridge[i] = bridge[i ^ 1] = true;
                min_bridge = min(min_bridge, z);
            }
        }
        else if(i != (in_edge ^ 1))
            low[x] = min(low[x], dfn[y]);
    }
}

int main()
{
    
    
    while(~scanf("%d%d", &n, &m) && n ){
    
    
        init();

        for(int i = 1; i <= m; ++ i){
    
    
            int x, y ,z;
            scanf("%d%d%d", &x, &y, &z);
            add(x, y, z), add(y, x, z);
        }
        dcc_cnt = 0;
        for(int i = 1; i <= n; ++ i)
            if(!dfn[i])
                tarjan(i, 0), dcc_cnt ++ ;
        
        if(dcc_cnt > 1){
    
    //tarjan多次说明不连通,直接输出0
            printf("0\n");
            continue;
        }
        if(min_bridge == INF)min_bridge = -1;
        else if(min_bridge == 0)min_bridge = 1;
        printf("%d\n", min_bridge);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45697774/article/details/108583000