#include <bits/stdc++.h>
using namespace std;
ifstream fin("dfs.in");
ofstream fout("dfs.out");
class graf
{
protected:
int n;
int m;
const static int nmax=100001;
vector<int> muchii[nmax]; //liste de adiacenta- array de vectori
int dist[nmax];
int viz[nmax];
void Reseteaza_viz();
void Reseteaza_dist();
void DFS(int start, vector<int> muchii_curente[]);
public:
graf(int noduri, int muchii):n(noduri), m(muchii) {}
int Comp_conexe();
void Afiseaza_distante();
void BFS(int start); //calculeaza si distantele de la
//start la fiecare nod
};
class graf_orientat: public graf
{
public:
graf_orientat(int noduri, int muchii):graf(noduri,muchii) {}
void Citire();
void CTC();// alg. lui Kosaraju
void SortareTopologica();
protected:
void DFStimpi(int nod, vector<int> muchii_curente[], stack<int>& timpi_final); //memoreaza si timpii de final
void DFSctc (int nod, vector<int> muchii_curente[], int nr_comp, vector<int> comp_tare_con[]);//memoreaza si comp tare conexe
void Graf_transpus(vector<int> muchii_transp[]);
};
class graf_neorientat: public graf
{
public:
graf_neorientat(int noduri, int muchii):graf(noduri,muchii) {}
void Citire();
void MuchiiCritice();
void Componente_biconexe();
protected:
void DFScritice(int nod, vector<int> muchii[], vector<vector<int> >& muchii_critice, int nivel[], int nivel_min[] );
void DFSbiconex(int nod, vector<int> muchii[], int nivel[], int nivel_min[], stack<int>& noduri_traversate, vector<vector<int>>& componente);
};
void graf_neorientat::DFSbiconex(int nod, vector<int> muchii[], int nivel[], int nivel_min[], stack<int>& noduri_traversate, vector<vector<int> >& componente)
{
int fiu;
viz[nod]=1;
noduri_traversate.push(nod);
nivel_min[nod]=nivel[nod];
for(int j=0; j<muchii[nod].size(); ++j)
{
fiu=muchii[nod][j];
if(!viz[fiu])
{
nivel[fiu]=nivel[nod]+1;
DFSbiconex(fiu, muchii, nivel,nivel_min,noduri_traversate,componente);
//formula A de la muchii critice
//actualizez nivelul minim al nodului curent deoarece fiul poate urca la un nod de deasupra celui curent
nivel_min[nod]=min(nivel_min[nod], nivel_min[fiu]);
//verific daca parintele e nod critic
if(nivel_min[fiu]>=nivel[nod])
{
vector<int> temp;
//elimin din stiva pana la fiu, apoi fiul si nodul curent
//nu se elimina pana la nodul curent direct deoarece se mai pot afla noduri
//de la alta componenta biconexa intre cel curent si fiu
while(noduri_traversate.top()!=fiu)
{
temp.push_back(noduri_traversate.top());
noduri_traversate.pop();
}
temp.push_back(fiu);
noduri_traversate.pop();
temp.push_back(nod);
//nu elimin nodul tata deoarece el poate face parte si din alta componenta biconexa
componente.push_back(temp);
}
}
else //muchie de intoarcere
if(nivel[fiu]<nivel[nod]-1)
//formula B de la muchii critice
nivel_min[nod]=min(nivel_min[nod], nivel[fiu]);
}
}
void graf_neorientat::Componente_biconexe()
{
//asemanator ca muchiile critice
//complexitate: O(n+m)
vector<vector<int>> componente;
stack<int> noduri_traversate;
int nivel[n+1];
int nivel_min[n+1];
Reseteaza_viz();
nivel[1]=1;
DFSbiconex(1,muchii,nivel,nivel_min,noduri_traversate,componente);
fout<<componente.size()<<"\n";
for(int i=0; i<componente.size(); ++i)
{
for(int j=0; j<componente[i].size(); ++j)
fout<<componente[i][j]<<" ";
fout<<"\n";
}
}
void graf_neorientat::DFScritice(int nod, vector<int> muchii[], vector<vector<int> >& muchii_critice, int nivel[], int nivel_min[])
{
int fiu;
vector<int> temp; //pentru lista de muchii critice
//2 valori random pe care le modific ulterior
temp.push_back(0);
temp.push_back(0);
viz[nod]=1;
//setez nivelul minim ca fiind cel curent
nivel_min[nod]=nivel[nod];
for(int j=0; j<muchii[nod].size(); ++j)
{
fiu=muchii[nod][j];
if(!viz[fiu])
{
nivel[fiu]=nivel[nod]+1; //actualizez nivelul fiului
DFScritice(fiu, muchii, muchii_critice,nivel,nivel_min);
//la intoarcerea din dfs, actualizez dupa formula A (curs)
nivel_min[nod]=min(nivel_min[nod], nivel_min[fiu]);
//verific daca e muchie critica
if(nivel_min[fiu]>nivel[nod])
{
temp[0]=nod;
temp[1]=fiu;
muchii_critice.push_back(temp);
}
}
else //muchie de intoarcere
if(nivel[fiu]<nivel[nod])
//formula B (curs)
//actualizez nivelul minim al nodului curent deoarece
//fiul este deasupra celui curent
nivel_min[nod]=min(nivel_min[nod], nivel[fiu]);
}
}
void graf_neorientat::MuchiiCritice()
{
//pentru fiecare nod calculez nivelul si nivelul minim la care poate ajunge
//actualizez nivelul minim in functie de caz si verific daca gasesc muchie critica
//complexitate: O(n+m)
int nivel[n+1];
int nivel_min[n+1];
vector<vector<int> > muchii_critice;
Reseteaza_viz();
nivel[1]=1;
DFScritice(1, muchii, muchii_critice, nivel, nivel_min);
for(int i=0; i<muchii_critice.size(); ++i)
{
for(int j=0; j<muchii_critice[i].size(); ++j)
fout<<muchii_critice[i][j]<<" ";
fout<<"\n";
}
}
void graf_orientat::SortareTopologica()
{
//complexitate O(n+m)
int grad_intern[n], vf, afisate=0;
queue<int> sortare;
for(int i=1; i<=n; i++)
grad_intern[i]=0;
//calculez gradele interne pentru noduri
for(int i=1; i<=n; ++i)
for(int j=0; j<muchii[i].size(); ++j)
grad_intern[muchii[i][j]]+=1;
//adaug toate nodurile cu grad intern 0 intr-o coada
for(int i=1; i<=n; ++i)
if(grad_intern[i]==0)
sortare.push(i);
while(!sortare.empty())
{
//extrag primul element din coada
vf=sortare.front();
sortare.pop();
fout<<vf<<" ";
afisate++;
//scad gradele vecinilor (elimin nodul curent in mod fictiv)
//adaug in coada nodurile cu noul grad intern 0
for(int j=0; j<muchii[vf].size(); ++j)
{
grad_intern[muchii[vf][j]]--;
if(grad_intern[muchii[vf][j]]==0)
sortare.push(muchii[vf][j]);
}
}
if(afisate!=n) //graful are cicluri
fout<<"Nu exista sortare topologica!";
fout<<"\n";
}
void graf_orientat::Graf_transpus(vector<int> muchii_transp[])
{
for(int i=1; i<=n; ++i)
for(int j=0; j<muchii[i].size(); ++j)
muchii_transp[muchii[i][j]].push_back(i);
}
void graf_orientat::DFStimpi(int nod, vector<int> muchii_curente[],stack<int> & timpi_final)
{
viz[nod]=1;
for(int i=0; i<muchii_curente[nod].size(); ++i)
if(!viz[muchii_curente[nod][i]])
DFStimpi(muchii_curente[nod][i], muchii_curente, timpi_final);
timpi_final.push(nod);//am terminat cu un nod, il adaug in stiva
}
void graf_orientat::DFSctc (int nod, vector<int> muchii_curente[], int nr_comp, vector<int> comp_tare_con[])
{
viz[nod]=1;
//adaug nodul la componenta tare conexa respectiva
comp_tare_con[nr_comp].push_back(nod);
for(int i=0; i<muchii_curente[nod].size(); ++i)
if(!viz[muchii_curente[nod][i]])
DFSctc(muchii_curente[nod][i], muchii_curente, nr_comp, comp_tare_con);
}
void graf_orientat::CTC()
{
// alg. lui Kosaraju
//complexitate: O(n+m)
int nr=0; //numarul de ctc
stack<int> timpi_final;
vector<int> muchii_transp[n+1]; //liste de adiacenta- array de vectori
vector<int> comp_tare_con[n+1]; //ctc, poz1- elem comp1, poz2-elem comp2 etc.
Reseteaza_viz();
//pas1: creez graful transpus (muchii inversate)
Graf_transpus(muchii_transp);
//pas2: dfs in care retin timpii de final(stiva) pe graful initial
for(int i=1; i<=n; ++i)
if(!viz[i])
DFStimpi(i, muchii, timpi_final);
Reseteaza_viz();
//pas3:dfs in functie de timpii de final pe graful transpus
while(!timpi_final.empty())
{
int vf=timpi_final.top();
timpi_final.pop();
if(!viz[vf])
{
nr++;
DFSctc(vf, muchii_transp, nr, comp_tare_con);
}
}
fout<<nr<<"\n";
for(int i=1; i<=nr; ++i)
{
for(int j=0; j<comp_tare_con[i].size(); ++j)
fout<<comp_tare_con[i][j]<<" ";
fout<<"\n";
}
}
void graf::Afiseaza_distante()
{
for(int i=1; i<=n; ++i)
fout<<dist[i]<<" ";
fout<<"\n";
}
int graf::Comp_conexe()
{
Reseteaza_viz();
int nr_comp=0;
for(int i=1; i<=n; ++i)
if(!viz[i])
{
DFS(i, muchii);
nr_comp++;
}
return nr_comp;
}
void graf::Reseteaza_dist()
{
for(int i=1; i<=n; ++i)
dist[i]=-1;
}
void graf::Reseteaza_viz()
{
for(int i=1; i<=n; ++i)
viz[i]=0;
}
void graf::DFS(int nod, vector<int> muchii_curente[])
{
//complexitate: O(n+m)
viz[nod]=1;
for(int i=0; i<muchii_curente[nod].size(); ++i)
if(!viz[muchii_curente[nod][i]])
DFS(muchii_curente[nod][i], muchii_curente);
}
void graf::BFS(int start)
{
//complexitate: O(n+m)
queue<int> coada;
Reseteaza_dist();
Reseteaza_viz();
viz[start]=1;
dist[start]=0;
coada.push(start);
while(!coada.empty())
{
int vf=coada.front();
coada.pop();
for(int i=0; i<muchii[vf].size(); ++i)
if(!viz[muchii[vf][i]])
{
viz[muchii[vf][i]]=1;
dist[muchii[vf][i]]=dist[vf]+1;
coada.push(muchii[vf][i]);
}
}
}
void graf_orientat::Citire()
{
int n1,n2;
for(int i=0; i<m; ++i)
{
fin>>n1>>n2;
muchii[n1].push_back(n2);
}
}
void graf_neorientat::Citire()
{
int n1,n2;
for(int i=0; i<m; ++i)
{
fin>>n1>>n2;
muchii[n1].push_back(n2);
muchii[n2].push_back(n1);
}
}
void HavelHakimi()
{
//complexitate: O(n^2 logn)
vector<int> grade;
int suma=0;
int n,grad;
//citire grade
while(fin>>grad)
{
grade.push_back(grad);
suma+=grad;
}
n=grade.size();
//daca suma e impara sau unul din grade>n-1 nu e corect
if(suma%2==1)
{
fout<<"NU\n";
return;
}
for(int i=0; i<n; ++i)
if(grade[i]>n-1)
{
fout<<"NU\n";
return;
}
//sortez descrescator gradele
sort(grade.begin(),grade.end(), greater<int> ());
while(grade[0]!=0)
{
//decrementez cu 1 gradele de la poz 1 la grade[0]
for(int i=1; i<=grade[0]; ++i)
{
grade[i]--;
if(grade[i]<0)
{
fout<<"NU\n";
return;
}
}
grade[0]=0;
sort(grade.begin(),grade.end(), greater<int> ());
}
fout<<"DA\n";
}
int main()
{
int n,m;
fin>>n>>m;
graf_neorientat g(n,m);
g.Citire();
fout<<g.Comp_conexe();
return 0;
}