#pragma GCC optimize "trapv"
#include<bits/stdc++.h>

#define int long long
#define pb push_back
#define ppb pop_back
#define ff first
#define ss second
#define sz(s) (int)(s.size())
#define all(a) (a).begin(),(a).end()
#define pii pair<int,int>
#define deb(x) cout << #x << " = " << x << endl
#define endl '\n'
#define M 1000000007
const int INF = 1ll << 60;

using namespace std;

// set<pair<int, int>> st;
vector<int> v[2001];
bool vis[2001];

void dfs(int ind, int &cnt) {

	vis[ind] = 1;
	for (auto x : v[ind]) {
		if (!vis[x]) {
			// st.insert({ind, x});
			cnt++;
			dfs(x, cnt);
		}
	}
}

void solve() {
	int n, m;
	cin >> n >> m;
	int a, b;
	while (m--) {
		cin >> a >> b;
		v[a].pb(b);
	}

	int ans = n;
	for (int i = 1; i <= n; ++i) {
		memset(vis, 0, sizeof(vis));
		int cnt = 0;
		dfs(i, cnt);
		ans += cnt;
	}

	cout << ans << endl;
}



signed main() {

	ios_base::sync_with_stdio(false);
	cin.tie(NULL);

	int t = 1;
	// cin >> t;
	while (t--) {
		solve();
	}
}

#pragma GCC optimize("Ofast")
#pragma GCC target("avx,avx2,fma")
#pragma GCC optimization("unroll-loops")

#include <bits/stdc++.h>
using namespace std;
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
#define ld long double
#define ll long long
#define REP(i, a, b) for (ll i = a; i < b; i++)
#define REPI(i, a, b) for (ll i = b - 1; i >= a; i--)
#define i_os ios::sync_with_stdio(0);  cin.tie(0);  cout.tie(0);
#define INF (ll)1e18 + 100
#define endl "\n"
#define p0(a) cout << a << " "
#define p1(a) cout << a << endl
#define p2(a, b) cout << a << " " << b << endl
#define p3(a, b, c) cout << a << " " << b << " " << c << endl
#define p4(a, b, c, d) cout << a << " " << b << " " << c << " " << d << endl
// SOME BITMASK KNOWLEDGE
// 1)x & (x - 1):sets the last one bit of x to zero
// power of two exactly when x & (x − 1) = 0.
// 2)x & -x:sets all the one bits to zero, except last one bit
// 3)x | (x - 1):inverts all the bits after the last one bit
#define o_set tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update>
#define o_setll tree<ll, null_type, less<ll>, rb_tree_tag, tree_order_statistics_node_update>
typedef tree<pair<ll, ll>,null_type,less<pair<ll, ll>>,rb_tree_tag,tree_order_statistics_node_update> indexed_set;
typedef tree<ll,null_type,less<ll>,rb_tree_tag,tree_order_statistics_node_update> indexed_set1;
typedef tree<string,null_type,less<string>,rb_tree_tag,tree_order_statistics_node_update> indexed_set2;
//1. order_of_key(k) : number of elements strictly lesser than k
//2. find_by_order(k) : k-th element in the set
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);

vector<ll> dp,possible;

ll memo(ll mask,ll n){
	if(dp[mask] != -1){
		return dp[mask];
	}
	ll ct = 0;
	REP(i,0,n){
        if((mask >> i) & 1) ct++;
    }
	dp[mask] = ct;
	for(ll s = mask; s > 0; s = (s - 1) & mask){
		if(possible[s]){
			dp[mask] = min(dp[mask],1 + memo((mask^s),n));
		}
    }
    return dp[mask];
}

int main()
{
	i_os;
	ll n,m;
	cin>>n>>m;
	set<ll> edges[n];
	REP(i,0,m){
		ll x,y;
		cin>>x>>y;
		x--; y--;
		edges[x].insert(y);
		edges[y].insert(x);
	}
	dp.resize((1 << n),-1);
	possible.resize((1 << n),0);
	possible[0] = 1;
	dp[0] = 0;
	REP(i,1,(1 << n)){
		vector<ll> v;
		REP(j,0,n){
			if((i >> j) & 1) v.push_back(j);
		}
		bool f = 0;
		REP(j,0,v.size()){
			REP(k,j+1,v.size()){
				auto it = edges[v[j]].find(v[k]);
				if(j != k && it == edges[v[j]].end()){
					f = 1;
					break;
				}
			}
			if(f) break;
		}
		if(f) possible[i] = 0;
		else possible[i] = 1;
	}
	cout<<memo((1 << n) - 1,n)<<endl;
	return 0;
}
