#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int ll

#define REP(i,n) for(int i=0;i<n;++i)
#define SORT(name) sort(name.begin(), name.end())
#define ZERO(p) memset(p, 0, sizeof(p))
#define MINUS(p) memset(p, -1, sizeof(p))
#if 1
#  define DBG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#  define DBG(fmt, ...)
#endif

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const double DINF = std::numeric_limits<double>::infinity();
const int MOD = 1000000007;
#define MAX_N 710

class UnionFind {
    public:
        UnionFind(){}
        UnionFind(int size) { Init(size); }
        // size: Union-Find で扱いたい要素の数
        void Init(int size) { m_Data.assign(size, -1); }

        // x と y の属する集合を併合
        // 既に一緒な集合内にいるなら false を
        // 新たに併合されたなら true を返す
        bool Unite(int x, int y) {
            x = Find(x), y = Find(y);
            if(x == y) { return (false); }
            if(m_Data[x] > m_Data[y]) { swap(x, y); }
            m_Data[x] += m_Data[y];
            m_Data[y] = x;
            return (true);
        }

        // 要素 k が属する集合の根を返す
        int Find(int k) {
            if(m_Data[k] < 0) { return (k); }
            return (m_Data[k] = Find(m_Data[k]));
        }

        // 要素 k と同じ集合の要素数を返す
        int Size(int k) { return (-m_Data[Find(k)]); }

        // 要素のうち独立しているグループの数 O(NlogN)
        int GetGroupNum() {
            map<int, bool> groups;
            for(int i = 0; i < m_Data.size(); ++i) { groups[Find(i)] = true; }
            return static_cast<int>(groups.size());
        }

    private:
        vector<int> m_Data;
};

// グラフの補グラフ（辺のある／なしを逆にしたもの）を取得します
// 自己辺は作成しません
// O(N^2 log N) N: 頂点数
void MakeCompGraph(vector< vector<int> >& graph, vector< vector<int> >& comp_graph) {
    // graph の頂点を念のためソート
    int n = graph.size();
    for(int i = 0; i < n; ++i) { SORT(graph[i]); }

    // v 内に val があるか
    auto BinSearch = [](const vector<int>& v, int val) -> int {
        int lb = 0, ub = v.size();
        while(lb < ub) {
            ll mid = (lb + ub) / 2;
            if(v[mid] == val) { return mid; }
            else if(val < v[mid]) { ub = mid; }
            else { lb = mid + 1; }
        }
        return -1;
    };

    comp_graph.assign(n, vector<int>());
    for(int i = 0; i < n; ++i) {
        for(int j = i + 1; j < n; ++j) {
            if(BinSearch(graph[i], j) == -1) {
                comp_graph[i].push_back(j);
                comp_graph[j].push_back(i);
            }
        }
    }
}

// 無向グラフを表す隣接リストとチェック開始頂点を与えると、
// チェック開始頂点から連結している成分が二部グラフかどうかを判定する
// O(NM) N: 頂点数 M: 辺数
enum NodeColor { None = 0, Red, Blue };
vector<int> color;
bool BipartiteDfs(const vector< vector<int> >& graph, int node_id) {
    int paint = (color[node_id] == Red) ? Blue : Red;
    for(auto e : graph[node_id]) {
        if(color[e] == None) {
            color[e] = paint;
            if(!BipartiteDfs(graph, e)) { return false; }
        }
        else if(color[e] != None && color[e] != paint) {
            return false;
        }
    }
    return true;
};
bool IsBipartite(const vector< vector<int> >& graph, int start_id) {
    color[start_id] = Red;
    return BipartiteDfs(graph, start_id);
}

int N, M;
vector< vector<int> > graph;        // グラフの隣接リスト
vector< vector<int> > comp_graph;   // 補グラフの隣接リスト
vector< vector<int> > connected;    // i が root のグループの頂点一覧
vector< pair<int, int> > bipar;     // 各二部グラフの同じ色の頂点の数 first: 赤色の数 second: 青色の数

int dp[MAX_N][MAX_N];
// i 番目のグループを見ていて、現在の赤色頂点の数が cnt の場合の
// 2つのクリークの辺数が最小になるときの数
int dfs(int i, int cnt) {
    if(i >= bipar.size()) {
        int red_cnt = cnt;
        int blue_cnt = N - cnt;
        return (red_cnt * (red_cnt - 1) / 2) + (blue_cnt * (blue_cnt - 1) / 2);
    }

    int& ret = dp[i][cnt];
    if(ret != INF) { return ret; }

    ret = dfs(i + 1, cnt + bipar[i].first);
    ret = min(ret, dfs(i + 1, cnt + bipar[i].second));

    return ret;
}

signed main()
{
    cin >> N >> M;

    UnionFind uf(N);
    graph.assign(N, vector<int>());

    REP(i, M) {
        int a, b;
        cin >> a >> b;
        a--; b--;
        graph[a].push_back(b);
        graph[b].push_back(a);
    }

    MakeCompGraph(graph, comp_graph);
    REP(i, N) {
        for(auto& n : comp_graph[i]) {
            uf.Unite(i, n);
        }
    }

    // 連結成分ごとのグループの vector を作成
    connected.assign(N, vector<int>());
    REP(i, N) {
        connected[uf.Find(i)].push_back(i);
    }

    // 連結成分ごとに二部グラフか判定
    color.assign(graph.size(), None);
    REP(i, N) {
        if(connected[i].size() > 0) {
            if(!IsBipartite(comp_graph, i)) {
                // 二部グラフにできないなら終了
                printf("-1\n");
                return 0;
            }
        }
    }

    // 連結成分を二部グラフにした際、片方の色がいくつになるかまとめる
    REP(i, N) {
        if(connected[i].size() > 0) {
            int red_cnt = 0, blue_cnt = 0;
            for(auto& n : connected[i]) {
                if(color[n] == Red) { red_cnt++; }
                else { blue_cnt++; }
            }
            bipar.push_back(make_pair(red_cnt, blue_cnt));
        }
    }

    // DP で二部グラフの各頂点をどっちの色に塗り分けた時が変数が一番少なくなるか計算
    REP(i, N) { REP(j, N) { dp[i][j] = INF; } }
    int ans = dfs(0, 0);

    printf("%lld\n", ans);

    return 0;
}


Measured memory_bytes: 10368