codefoces_#346E - New Reform(并查集或dfs)

来源:互联网 发布:java sop的理解 编辑:程序博客网 时间:2024/05/16 13:58

E. New Reform

time limit per test1 second
memory limit per test256 megabytes
inputstandard input
outputstandard output
Berland has n cities connected by m bidirectional roads. No road connects a city to itself, and each pair of cities is connected by no more than one road. It is not guaranteed that you can get from any city to any other one, using only the existing roads.

The President of Berland decided to make changes to the road system and instructed the Ministry of Transport to make this reform. Now, each road should be unidirectional (only lead from one city to another).

In order not to cause great resentment among residents, the reform needs to be conducted so that there can be as few separate cities as possible. A city is considered separate, if no road leads into it, while it is allowed to have roads leading from this city.

Help the Ministry of Transport to find the minimum possible number of separate cities after the reform.

Input
The first line of the input contains two positive integers, n and m — the number of the cities and the number of roads in Berland (2 ≤ n ≤ 100 000, 1 ≤ m ≤ 100 000).

Next m lines contain the descriptions of the roads: the i-th road is determined by two distinct integers xi, yi (1 ≤ xi, yi ≤ n, xi ≠ yi), where xi and yi are the numbers of the cities connected by the i-th road.

It is guaranteed that there is no more than one road between each pair of cities, but it is not guaranteed that from any city you can get to any other one, using only roads.

Output
Print a single integer — the minimum number of separated cities after the reform.

Examples
input
4 3
2 1
1 3
4 3
output
1
input
5 5
2 1
1 3
2 3
2 5
4 3
output
0
input
6 5
1 2
2 3
4 5
4 6
5 6
output
1
题意:给定n个节点,m条路径,m条路径的方向可以随意,既可以为u指向v,也可以为v指向u,但是单向的,
求出最少多少个节点入度为0.

不看题解之前,是想用并查集的。
看了题解之后,题目说可以用bfs或者dfs走一遍,如果节点已访问,则不继续访问。
解题思路:
对于题目可能形成多个不相交的连通图
对于n个节点n-1条边的连通图,除了根节点其余节点的入度都为1
所以有1个节点入度为0
对于有环的连通图,根节点从环中的节点取,都能发现所有节点的入度都能大于等于1(环的个数大于等于1)
方法一:
一开始,用数组的邻接表存储,将路径的方向指定成节点号较小的指向大节点,然后对n个节点循环一次,若未经过就dfs一遍,若dfs的所有节点都未访问过就countt加1。
然而我提交之后,老是wrong answer
最后发现bug
原来出现在以下情况 1->8->20, 2->7->21
3->20, 3->21
dfs(1)和dfs(2)后count变为2
dfs(3)时
由于20与21走过所以形成圈
count仍为2
实际情况count此时为1,1->8->20->3->21->7->2
所以出现wa
提交wa的代码

#include <cstdio>#include <cstring>#include <iostream>using namespace std;int head[100010];//邻接表的头节点struct edge {    int u;    int v;    int next;};edge e[100010];//边int visit[100010];//节点是否访问,0为未访问,1为已访问int countt = 0;//入度为0的点的个数int len = 0;int flag = 0;//是否形成圈void add(int u, int v) {    if (u > v)        swap(u, v);//指定路径方向,小节点指向大节点    e[len].u = u;    e[len].v = v;    e[len].next = head[u];    head[u] = len++;}void dfs(int x) {    for (int i = head[x]; i != -1; i = e[i].next) {        if (!visit[e[i].v]) {            visit[e[i].v] = 1;            dfs(e[i].v);        } else {            flag = 0;        }    }}int main() {    for (int i = 0; i < 100010; i++)        head[i] = -1;    memset(visit, 0, sizeof(visit));    int n, m, u, v;    scanf("%d%d", &n, &m);    for (int i = 0; i < m; i++) {        scanf("%d%d", &u, &v);        add(u, v);    }    for (int i = 1; i <= n; i++) {        if (!visit[i]) {            flag = 1;            visit[i] = 1;            dfs(i);            countt += flag;//形成圈加1        }    }    printf("%d\n", countt);    return 0;}

之后我就发现只要把路径添加为双向的,这时虽然给判断是否为圈增加难度。
解决这个问题我就想到,当经过路径u->v后,发现v->u只会在下一个dfs被访问到,所以每次dfs都保存好前一条边的信息就好了。

#include <cstdio>#include <cstring>#include <iostream>using namespace std;int head[100010];//邻接表的头节点struct edge {    int u;    int v;    int next;//指向下一条以u为起点的边};edge e[200010];//所有边的信息int visit[100010];//记录节点是否访问,0为未访问,1为访问int countt = 0;//入度为0的节点数int len = 0;//边数int flag = 0;//是否形成圈void add(int u, int v) {    e[len].u = u;    e[len].v = v;    e[len].next = head[u];    head[u] = len++;}void dfs(int x, int pre) {    for (int i = head[x]; i != -1; i = e[i].next) {        if (!visit[e[i].v] && e[i].v != pre) {//未访问节点            visit[e[i].v] = 1;//已访问            dfs(e[i].v,  e[i].u);        } else if (e[i].v != pre) { //若pre前起点!=下一访问节点            flag = 0;//形成圈        }    }}int main() {    for (int i = 0; i < 100010; i++)        head[i] = -1;    memset(visit, 0, sizeof(visit));    int n, m, u, v;    scanf("%d%d", &n, &m);    for (int i = 0; i < m; i++) {        scanf("%d%d", &u, &v);        //添加双向路径        add(u, v);        add(v, u);    }    for (int i = 1; i <= n; i++) {        if (!visit[i]) {            flag = 1;            visit[i] = 1;            dfs(i, -1);            countt += flag;//形成圈加1        }    }    printf("%d\n", countt);    return 0;}

方法二:
并查集就是将能通过路径相互联系的节点进行关联
所以只需要在并查集的基础上判断是否有环生成,无环生成的countt+1;

#include <cstdio>using namespace std;int f[100010];int find(int x) {    if (f[x] == x)        return f[x];    else        return f[x] = find(f[x]);}int cicle[100010];int main() {    memset(cicle, 0, sizeof(cicle));    int n, m, x, y, countt = 0;    scanf("%d%d", &n, &m);    for (int i = 1;i <= n; i++)        f[i] = i;    for (int i = 1; i <= m; i++) {        scanf("%d%d", &x, &y);        int z = find(x);        int k = find(y);        if (z == k)//若相等,证明有环            cicle[z] = z;        else {            f[z] = k;            if (cicle[z])//若z有环,联系之后k必然有环                cicle[k] = cicle[z];        }    }    for (int i = 1; i <= n; i++) {        if (f[i] == i && !cicle[i])//节点为根节点且无环countt+1            countt++;    }    printf("%d\n", countt);    return 0;}
0 0