并查集
A - How Many Answers Are Wrong
题意:
已知区间[1,n],给出m组数据,即[l,r]区间内数据之和为s,求错误数据的数量。
拿到这道题,真的没思路,知道用并查集,但是关于如何定义s迟迟没有思路,就开始了搜文章学习(划水)的历程。
下面就记录一些自己的体会:
1、并查集中路径压缩的过程中如何更新关系域是关键;
2、根据数据定义一些合理的关系(如:集合中的根与集合中的元素的关系),才能对集合进行合并。这里可能会分一些情况讨论,最终应该是可以化简的。
然后放出学习的文章(感谢作者):
里面有关向量的思考和设计很巧妙
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <cstdio>
2 #include <algorithm>
3 #include <cstring>
4 using namespace std;
5 const int maxn = 200020;
6 int f[maxn];
7 int sum[maxn]; //记录当前结点到根的距离
8 int find(int x){
9 //return x==f[x]?x:find(f[x]);
10 if(x != f[x]){
11 int roota = f[x];
12 f[x] = find(f[x]);
13 sum[x] += sum[roota];
14 //x->rootb = x->roota + roota->rootb
15 }
16 return f[x];
17 }
18 int main(){
19 //freopen("in.txt","r",stdin);
20 int n,m;
21 while(scanf("%d%d", &n, &m) != EOF){
22 for(int i=0; i<=n; i++){
23 f[i] = i;
24 sum[i] = 0;
25 }
26 int ans = 0;
27 while(m--){
28 int a, b, v;
29 scanf("%d%d%d", &a, &b, &v);
30 a--;
31 //join
32 int roota = find(a);
33 int rootb = find(b);
34 if(roota == rootb){
35 if(sum[a]-sum[b] != v) ans++;
36 }else{
37 f[roota] = rootb;
38 //定义大的数字是小的数字的根
39 sum[roota] = -sum[a] + sum[b] + v;
40 //变换成等式 sum[roota] + sum[a] = sum[b] + v;
41 //a->roota + roota->rootb == b->rootb + a->b
42 }
43 //join
44 }
45 printf("%d\n",ans);
46 }
47 return 0;
48 }
B - 食物链
摘出上博客中的一句话:
对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的(这点是并查集的实质),否则也不会被合并到当前集合中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 const int maxn = 50010;
6 int f[maxn], relation[maxn];
7 //relation[i]:i到根的偏移量
8 int sum;
9 void init(int n){
10 for(int i=0; i<n; i++){
11 f[i] = i;
12 relation[i] = 0;
13 }
14 sum = 0;
15 }
16 int find(int x){
17 if(x != f[x]){
18 int rootx = f[x];
19 f[x] = find(rootx);
20 //路径压缩更新关系域//这是关键1
21 relation[x] = (relation[x] + relation[rootx]) % 3;
22 }
23 return f[x];
24 }
25 void join(int x, int y, int d){
26 int rootx = find(x);
27 int rooty = find(y);
28 if(rootx != rooty){
29 f[rooty] = rootx;
30 relation[rooty] = (3 + d-1 + relation[x] - relation[y]) % 3;
31 //此处d-1题中给出的询问已知条件即0同类,1吃,2被吃
32 /*先用等式表达:
33 这里并非简单的加减,实质应该是关系的转移,可以用向量理解
34 r->rx + x->y = y->ry + ry->rx
35 ====>r[x] + k = r[y] + ry->rx
36 ====>ry->rx = r[x] + k - r[y]
37 k = d - 1
38 同理下面两式也得
39 */
40 }else{
41 if((d==1) && (relation[x] != relation[y])){
42 sum++;
43 }else if((d==2) && ((d-1) != (relation[y] - relation[x] + 3) % 3)){
44 sum++;
45 }
46 }
47 }
48 int main(){
49 //freopen("in.txt","r",stdin);
50 int n, k;
51 scanf("%d%d", &n, &k);
52 init(n);
53 for(int i=0; i<k; i++){
54 int d, x, y;
55 scanf("%d%d%d",&d, &x, &y);
56 if(x > n || y > n){
57 sum++;
58 }else if((d==2) && (x == y)){
59 sum++;
60 }else{
61 join(x, y, d);
62 }
63 }
64 printf("%d\n",sum);
65 return 0;
66 }
C - A Bug's Life
题意:给定n个bugs,编号依次1、2、……n。它们之间只有雄性和雌性之分,并给定m对交配的情况,根据这m对交配的情况,判断是否有同性恋出现的情况。
Thinking:
定义relation[i]:结点i到根的距离:即相对与根节点的性别。
x,y在同一集合,他们相对根的距离即相对根的性别,而他们又同根,易得x,y的相对性别
x,y不再同一集合,思考向量得关系: y->x + x->rootx = y->rooty + rooty->rootx 其中y->x应为1。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <cstdio>
2 const int maxn = 2010;
3 int f[maxn];
4 int relation[maxn];
5 bool flag;
6 void init(int n){
7 //这里n没有初始完( i < n )导致WA了半天
8 for(int i=0; i<=n; i++){
9 f[i] = i;
10 relation[i] = 0;
11 }
12 }
13 int find(int x){
14 if(x != f[x]){
15 int rootx = f[x];
16 f[x] = find(rootx);
17 relation[x] = (relation[rootx] + relation[x]) % 2;
18 }
19 return f[x];
20 }
21 void join(int x, int y){
22 int rootx = find(x);
23 int rooty = find(y);
24 if(rootx == rooty){
25 if(relation[x] == relation[y]){
26 flag = true;
27 }
28 }else{
29 /*A了这题后在网上看到很多解法中这里对x,y进行分类,
30 即 x<y :
31 f[rooty] = rootx;
32 relation[rooty] = ((relation[x]+1) + relation[y]) % 2;
33 x>y :
34 f[rootx] = rooty;
35 relation[rootx] = ((relation[y]+1)%2 + relation[x]) % 2;
36 这里我认为应该可以不需要,因为查找过程中进行了路径压缩
37 */
38 f[rooty] = rootx;
39 relation[rooty] = ((relation[x]+1) - relation[y]) % 2;
40 //这里+由向量y->x + x->rootx = y->rooty + rooty->rootx得应该是-,但%2好像算术上是等价的
41 //这里我个人认为是减,当然%2时加和减结果一样, 这里留待思考?????
42 }
43 }
44 int main(){
45 //freopen("in.txt","r",stdin);
46 int T;
47 scanf("%d", &T);
48 for(int t=1; t<=T; t++){
49 int b, inter;
50 scanf("%d%d",&b, &inter);
51 init(b);
52 flag = false;
53 for(int i=0; i<inter; i++){
54 int x, y;
55 scanf("%d%d", &x, &y);
56 if(flag){
57 continue;
58 }else{
59 join(x, y);
60 }
61 }
62 printf("Scenario #%d:\n", t);
63 if(flag){
64 printf("Suspicious bugs found!\n\n");
65 }else{
66 printf("No suspicious bugs found!\n\n");
67 }
68 }
69 return 0;
70 }
D - Supermarket
并查集,不太想做了,以后补
无向图的割点
E - SPF
题意:一个网络中割点的个数,和割点可以将网络分成几部分。
Thinking:
不用belong记录强连通分量集合的编号,需要加入parent[]记录父节点,child判断子树数量。这是为了分开计算割点是根 和非根的情况。
割点的判断:number[u]<=low[v]即v必须通过u才能访问u的祖先,这里u就是割点,可以参考上面列的第二篇文章。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 const int maxn = 1005;
6 struct Node{
7 int to;
8 int next;
9 }edge[maxn*maxn];
10 int head[maxn], E, V;
11 void add_edge(int u, int v){
12 edge[E].to = v; edge[E].next = head[u]; head[u] = E++;
13 edge[E].to = u; edge[E].next = head[v]; head[v] = E++;
14 V = max(max(u,v), V);
15 }
16 int size_[maxn];
17 // 记录删除i结点后的连通分量个数
18 int low[maxn], number[maxn], parent[maxn];
19 ////Let LOWPT(v) be the smallest vertex in the set ${v}\bigcap { w| v \xrightarrow{*} -\rightarrow w}$
20 ///number[u]为结点u搜索的次序编号
21 ///parent[i]:记录i的父亲结点
22 bool flag; ///是否有割点存在
23 int cnt;
24
25 void dfs(int u){
26 int child = 0;
27 //记录子树的数量
28 low[u] = number[u] = ++cnt;
29 for(int i=head[u]; i!=-1; i=edge[i].next){
30 int v = edge[i].to;
31 if(!number[v]){
32 child++;
33 parent[v] = u;
34 dfs(v);
35 ///dfs(v)更新low[v]
36 low[u] = min(low[u], low[v]);
37 //必须通过u,v才能访问u的祖先number[u]<=low[v]
38 if((parent[u]==-1 && child>=2) || (parent[u]!=-1 && number[u]<=low[v])){
39 /*
40 child>2保证如果是根节点,将比非根节点少加1
41 因为如果割点不是根节点,其连通分量要加上其父辈
42 */
43 flag = true;
44 size_[u]++;
45 }
46 }else{
47 low[u] = min(low[u], number[v]);
48 }
49 }
50 }
51 void init(){
52 memset(size_, 0, sizeof(size_));
53 memset(number, 0, sizeof(number));
54 memset(low, 0, sizeof(low));
55 memset(head, -1, sizeof(head));
56 memset(parent, -1, sizeof(parent));
57 E = V = 0;
58 flag = false;
59 cnt = 0;
60 }
61 int main(){
62 // freopen("in.txt","r", stdin);
63 int cas;
64 for(int tt=1; ;tt++){
65 init();
66 //input
67 int u, v=-1;
68 while(scanf("%d", &u) && u){
69 scanf("%d", &v);
70 add_edge(u, v);
71 }
72 if(v == -1) break;
73 //input
74 dfs(V);
75 printf("Network #%d\n",tt);
76 if(flag){
77 for(int i=1; i <= V; i++){
78 if(size_[i] > 0){
79 printf(" SPF node %d leaves %d subnets\n",i,size_[i]+1);
80 }
81 }
82 }else{
83 printf(" No SPF nodes\n");
84 }
85 printf("\n");
86 }
87 return 0;
88 }
update(2018-08-30):
1、 pre[v]<pre[u]&&v!=f 理解:在无向图中edge(u, f)不是反向边(第一次处理时从后代指向祖先的边),只是树边edge(f, u)的第二次访问。
2、 if(lowv > pre[u]) { u->v is bridge} 这是对桥的判断。
3、在无向连通图G的dfs树中,非根结点u是G的割点当且仅当u存在一个子节点v,使得v及其所有后代都没有反向边连回u的祖先(连回u不算)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <algorithm>
5 #include <vector>
6 #include <stack>
7 using namespace std;
8
9 const int maxn = 1010;
10 vector<int> G[maxn];
11
12 int iscut[maxn];
13
14 int low[maxn], pre[maxn];
15 //pre:时间戳
16 //low[u]:u及其后代所能连回的最早祖先的pre值
17 int cnt, V;
18
19 int dfs(int u, int f){
20 int lowu = pre[u] = ++cnt;
21 int child = 0; //子节点数目
22 for(int i=0; i<G[u].size(); i++){
23 int v = G[u][i];
24 if(!pre[v]){ //未访问v
25 child++;
26 int lowv = dfs(v, u);
27 lowu = min(lowv, lowu); //用后代low更新low u
28 if(lowv >= pre[u]){
29 iscut[u]++;
30 }
31 }else if(pre[v]<pre[u] && v!=f){
32 //v==f无向图,此边不属于反向边,属于树边的二次访问
33 lowu = min(lowu, pre[v]); //反向边更新u
34 }
35 }
36 if(f<0 && child==1){ //根节点特判
37 iscut[u] = 0;
38 }
39 return low[u] = lowu;
40 }
41 void add_edge(int a, int b){
42 G[a].push_back(b); G[b].push_back(a);
43 V = max(max(a, b), V);
44 }
45 void init(){
46 memset(pre, 0, sizeof(pre));
47 memset(iscut, 0, sizeof(iscut));
48 for(int i=0; i<maxn; i++){ G[i].clear(); }
49 cnt = 0; V = 0;
50 }
51 int main(){
52 //freopen("in.txt", "r", stdin);
53 for(int tt=1; ;tt++){
54 init();
55 int u, v=-1;
56 while(scanf("%d", &u) && u){
57 scanf("%d", &v);
58 add_edge(u, v);
59 }
60 if(v == -1) break;
61 dfs(V, -1);
62 printf("Network #%d\n", tt);
63 bool flag = true;
64 for(int i=0; i<=V; i++){
65 if(iscut[i]){
66 printf(" SPF node %d leaves %d subnets\n", i, iscut[i]+1);
67 flag = false;
68 }
69 }
70 if(flag){
71 printf(" No SPF nodes\n");
72 }puts("");
73 }
74 return 0;
75 }
76
77
有向图的强连通分量
完成了一个Tarjan的算法实现,第一次写,有待优化。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
F - Proving Equivalences
题意:给一个有向图,试求添加多少条边可以使该图成为强连通图。
Thinking:
要利用DAG的性质,则需要用Tarjan算法缩点,将有向图转为DAG。
至于添加多少条边,可以这样想,怎样操作能构成最大环,即将DAG头尾相连即可。即求入度和出度的max();
需要特判强连通分量个数为一的情况如(1,2)(2,1),则不需要加边。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <stack>
5 using namespace std;
6 const int maxn = 20010;
7 struct Node{
8 int to;
9 int next;
10 }edge[maxn * 3];
11 int head[maxn],cnt;
12 stack<int> S;
13 int low[maxn], number[maxn];
14 int belong[maxn], scc;
15 //保存每个点属于的强连通集合的编号; 强连通分量编号最大值(即个数
16 void add_edge(int u, int v){
17 edge[++cnt].to = v;
18 edge[cnt].next = head[u];
19 head[u] = cnt;
20 }
21 void dfs(int u){
22 S.push(u);
23 number[u] = low[u] = ++cnt;
24 for(int i = head[u]; i!=-1; i = edge[i].next){
25 int v = edge[i].to;
26 if(!number[v]){
27 dfs(v);
28 low[u] = min(low[u], low[v]);
29 }else if(!belong[v]){
30 low[u] = min(low[u], number[v]);
31 }
32 }
33 if(number[u] == low[u]){
34 scc++;
35 int v;
36 do{
37 v = S.top(), S.pop();
38 belong[v] = scc;
39 }while(u != v);
40 }
41 }
42 int in[maxn], out[maxn];
43 void init(){
44 cnt = scc = 0;
45 memset(head, -1, sizeof(head));
46 memset(number, 0, sizeof(number));
47 memset(low, 0, sizeof(low));
48 memset(in, 0, sizeof(in));
49 memset(out, 0, sizeof(out));
50 memset(belong, 0, sizeof(belong));
51 while(!S.empty()) S.pop();
52 }
53 int main(){
54 //freopen("in.txt", "r", stdin);
55 int T;
56 scanf("%d", &T);
57 while(T--){
58 init();
59 int n, m;
60 scanf("%d%d", &n, &m);
61 for(int i=0; i<m; i++){
62 int u, v;
63 scanf("%d%d", &u, &v);
64 add_edge(u, v);
65 }
66 cnt = 0;
67 for(int i=1; i<=n; i++){
68 if(!number[i]){
69 dfs(i);
70 }
71 }
72 if(scc == 1){
73 printf("0\n");
74 continue;
75 }
76 for(int i=1; i<=n; i++){
77 for(int j=head[i]; j!=-1; j=edge[j].next){
78 int v = edge[j].to;
79 if(belong[v] != belong[i]){
80 in[belong[v]]++;
81 out[belong[i]]++;
82 }
83 }
84 }
85 int ans1 = 0, ans2 = 0;
86 for(int i=1; i<=scc; i++){
87 if(!out[i]) ans1++;
88 if(!in[i]) ans2++;
89 }
90 printf("%d\n",max(ans1, ans2));
91 }
92 return 0;
93 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 20010; 5 vector<int> G[maxn]; 6 stack<int> S; 7 int pre[maxn], lowlink[maxn], sccno[maxn], dfs_clock, scc_cnt; 8 //sccno[i]: i所在的SCC编号 9 void dfs(int u){ 10 pre[u] = lowlink[u] = ++dfs_clock; 11 S.push(u); 12 for(int i=0; i<G[u].size(); i++){ 13 int v = G[u][i]; 14 if(!pre[v]){ 15 dfs(v); 16 lowlink[u] = min(lowlink[u], lowlink[v]); 17 }else if(!sccno[v]){ 18 lowlink[u] = min(lowlink[u], pre[v]); 19 } 20 } 21 if(lowlink[u] == pre[u]){ 22 scc_cnt++; 23 while(true){ 24 int x = S.top(); S.pop(); 25 sccno[x] = scc_cnt; 26 if(x == u) break; 27 } 28 } 29 } 30 int find_scc(int n){ 31 dfs_clock = scc_cnt = 0; 32 memset(sccno, 0, sizeof(sccno)); 33 memset(pre, 0, sizeof(pre)); 34 for(int i=0; i<n; i++){ 35 if(!pre[i]) dfs(i); 36 } 37 } 38 int in[maxn], out[maxn]; 39 40 int main(){ 41 //freopen("in.txt", "r", stdin); 42 int t; scanf("%d", &t); 43 while(t--){ 44 int n, m; scanf("%d%d", &n, &m); 45 for(int i=0; i<n; i++) G[i].clear(); 46 for(int i=0; i<m; i++){ 47 int a, b; scanf("%d%d", &a, &b); a--, b--; 48 G[a].push_back(b); 49 } 50 find_scc(n); 51 // 对缩点后的DAG入度和出度计算 52 //in[i]:i的入度为0 53 for(int i=1; i<=scc_cnt; i++) in[i] = out[i] = 1; 54 for(int u=0; u<n; u++){ 55 for(int i=0; i<G[u].size(); i++){ 56 int v = G[u][i]; 57 if(sccno[u] != sccno[v]) in[sccno[v]] = out[sccno[u]] = 0; 58 //u,v对应的出入度不为0 59 } 60 } 61 // 62 int a = 0, b = 0; 63 for(int i=1; i<=scc_cnt; i++){ 64 if(in[i]) a++; 65 if(out[i]) b++; 66 } 67 int ans = max(a, b); 68 if(scc_cnt == 1) ans = 0; 69 printf("%d\n", ans); 70 } 71 return 0; 72 }
G - The Largest Clique
题意:给一个有向图G,求一个结点数最大的结点集(任意两点相互可达)。
Targan缩点 + DAG上的最长路。 给一个含圈的有向图,求最长路。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 1010; 5 vector<int> G[maxn], mp[maxn]; 6 stack<int> S; 7 int pre[maxn], lowlink[maxn], sccno[maxn], dfs_clock, scc_cnt; 8 //sccno[i]: i所在的SCC编号 9 void dfs(int u){ 10 pre[u] = lowlink[u] = ++dfs_clock; 11 S.push(u); 12 for(int i=0; i<G[u].size(); i++){ 13 int v = G[u][i]; 14 if(!pre[v]){ 15 dfs(v); 16 lowlink[u] = min(lowlink[u], lowlink[v]); 17 }else if(!sccno[v]){ 18 lowlink[u] = min(lowlink[u], pre[v]); 19 } 20 } 21 if(lowlink[u] == pre[u]){ 22 scc_cnt++; 23 int x; 24 do{ 25 x = S.top(); S.pop(); 26 sccno[x] = scc_cnt; 27 }while(x != u); 28 } 29 } 30 void find_scc(int n){ 31 dfs_clock = scc_cnt = 0; 32 memset(sccno, 0, sizeof(sccno)); 33 memset(pre, 0, sizeof(pre)); 34 for(int i=0; i<n; i++){ 35 if(!pre[i]) dfs(i); 36 } 37 } 38 int size_[maxn], d[maxn]; 39 40 //DAG上的最长距离 41 int dp(int u) { 42 if(d[u] >= 0) return d[u]; 43 d[u] = size_[u]; 44 for(int i = 0; i < mp[u].size(); i++){ 45 int v = mp[u][i]; 46 d[u] = max(d[u], dp(v) + size_[u]); 47 } 48 return d[u]; 49 } 50 void build(int n){ 51 for(int i=0; i<=scc_cnt; i++) mp[i].clear(); 52 memset(size_, 0, sizeof(size_)); 53 for(int i=0; i<n; i++) size_[sccno[i]]++; 54 for(int i=0; i<n; i++){ 55 for(int j=0; j<G[i].size(); j++){ 56 int v = G[i][j]; 57 if(sccno[i] != sccno[v]){ 58 mp[sccno[i]].push_back(sccno[v]); 59 } 60 } 61 } 62 } 63 int main(){ 64 // freopen("in.txt", "r", stdin); 65 int t; scanf("%d", &t); 66 while(t--){ 67 int n, m; scanf("%d%d", &n, &m); 68 for(int i=0; i<n; i++) G[i].clear(); 69 for(int i=0; i<m; i++){ 70 int a, b; scanf("%d%d", &a, &b); a--; b--; 71 G[a].push_back(b); 72 } 73 74 find_scc(n); 75 76 build(n); 77 78 int ans = 0; 79 memset(d, -1, sizeof(d)); 80 for(int i=1; i<=scc_cnt; i++){ 81 ans = max(ans, dp(i)); 82 } 83 printf("%d\n", ans); 84 } 85 return 0; 86 }
无向图的双连通分量
K - Knights of the Round Table(uva 3523)
题意:n个骑士举行会议,每次圆桌会议至少三人,且骑士数目为奇数,相互憎恨的骑士不能再相邻位置,问多少个骑士不能参加会议。
二分图 + BCC
建图:两个骑士可以相邻,则建一条无向边。
求不在任何一个简奇圈上的结点个数。
简单圈上的所有结点必然属于同一个双连通分量;二分图没有奇圈。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 1010; 4 5 struct edge{ 6 int u, v; 7 edge(int a, int b) : u(a), v(b) {} 8 }; 9 int pre[maxn], iscut[maxn], bccno[maxn], dfs_clock, bcc_cnt; 10 //bccno[i]=x:第i个顶点属于第x个点-双连通分量 11 vector<int> G[maxn], bcc[maxn]; 12 //bcc[i]: 编号为i的点-双连通分量的所有结点 13 stack<edge> S; 14 15 int dfs(int u, int f){ 16 int lowu = pre[u] = ++dfs_clock; 17 int child = 0; 18 for(int i=0; i<G[u].size(); i++){ 19 int v = G[u][i]; 20 edge e = (edge){u, v}; 21 if(!pre[v]){ 22 S.push(e); 23 child++; 24 int lowv = dfs(v, u); 25 if(lowv >= pre[u]){ 26 iscut[u]++; 27 bcc_cnt++; bcc[bcc_cnt].clear(); 28 while(true){ 29 edge x = S.top(); S.pop(); 30 if(bccno[x.u] != bcc_cnt){ 31 bcc[bcc_cnt].push_back(x.u); 32 bccno[x.u] = bcc_cnt; 33 } 34 if(bccno[x.v] != bcc_cnt){ 35 bcc[bcc_cnt].push_back(x.v); 36 bccno[x.v] = bcc_cnt; 37 } 38 if(x.u == u && x.v == v){ 39 break; 40 } 41 } 42 } 43 }else if(pre[v] < pre[u] && v != f){ 44 S.push(e); 45 lowu = min(lowu, pre[v]); 46 } 47 } 48 if(f < 0 && child==1) iscut[u]=0; 49 return lowu; 50 } 51 52 void find_bcc(int n){ 53 memset(pre, 0, sizeof(pre)); 54 memset(iscut, 0, sizeof(iscut)); 55 memset(bccno, 0, sizeof(bccno)); 56 dfs_clock = bcc_cnt = 0; 57 for(int i=0; i<n; i++){ 58 if(!pre[i]) dfs(i, -1); 59 } 60 } 61 int color[maxn]; 62 bool odd[maxn]; 63 bool bipartite(int u, int b){ 64 for(int i=0; i<G[u].size(); i++){ 65 int v = G[u][i]; 66 if(bccno[v] != b) continue; 67 if(color[v] == color[u]){ 68 return false; 69 } 70 if(!color[v]){ 71 color[v] = 3 - color[u]; 72 if(!bipartite(v, b)) return false; 73 } 74 } 75 return true; 76 } 77 int A[maxn][maxn]; 78 79 int main(){ 80 //freopen("in.txt", "r", stdin); 81 int kase = 0, n, m; 82 while(scanf("%d%d", &n, &m) != EOF && n){ 83 for(int i=0;i<n;i++)G[i].clear(); 84 memset(A, 0, sizeof(A)); 85 86 for(int i=0;i<m;i++){ 87 int a,b; scanf("%d%d",&a, &b); a--,b--; 88 A[a][b] = A[b][a] = 1; 89 } 90 for(int i=0; i<n; i++){ 91 for(int j=i+1; j<n; j++){ 92 if(!A[i][j]) G[i].push_back(j), G[j].push_back(i); 93 } 94 } 95 96 find_bcc(n); 97 98 memset(odd, 0, sizeof(odd)); 99 for(int i=1; i<=bcc_cnt; i++){ 100 for(int j=0; j<bcc[i].size(); j++){ //同一个BBC内统一编号 101 bccno[bcc[i][j]] = i; 102 } 103 int u = bcc[i][0]; 104 105 memset(color, 0, sizeof(color)); 106 color[u] = 1; 107 if(!bipartite(u, i)){ //不是二分图,标记为奇圈 108 for(int j=0; j<bcc[i].size(); j++){ 109 odd[bcc[i][j]] = true; 110 } 111 } 112 } 113 int ans = n; 114 for(int i=0; i<n; i++) if(odd[i]) ans--; 115 printf("%d\n", ans); 116 } 117 return 0; 118 }