#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N, M;
cin >> N >> M;
vector<vector<int>> g(N + 1);
for (int i = 0; i < M; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
// ── Step 1: Find BCCs (iterative Tarjan) ─────────────────────────────────
vector<int> disc(N + 1, -1), low(N + 1, 0);
int timer = 0;
vector<int> vstk;
vector<vector<int>> bccs;
vector<tuple<int,int,int>> call_stack;
for (int start = 1; start <= N; start++) {
if (disc[start] != -1) continue;
disc[start] = low[start] = timer++;
vstk.push_back(start);
call_stack.push_back({start, -1, 0});
while (!call_stack.empty()) {
auto& [u, p, idx] = call_stack.back();
if (idx < (int)g[u].size()) {
int v = g[u][idx++];
if (v == p) continue;
if (disc[v] == -1) {
disc[v] = low[v] = timer++;
vstk.push_back(v);
call_stack.push_back({v, u, 0});
} else {
low[u] = min(low[u], disc[v]);
}
} else {
call_stack.pop_back();
if (!call_stack.empty()) {
auto& [pu, pp, pi] = call_stack.back();
low[pu] = min(low[pu], low[u]);
if (low[u] >= disc[pu]) {
vector<int> comp;
while (vstk.back() != u) { comp.push_back(vstk.back()); vstk.pop_back(); }
comp.push_back(u); vstk.pop_back();
comp.push_back(pu);
sort(comp.begin(), comp.end());
comp.erase(unique(comp.begin(), comp.end()), comp.end());
bccs.push_back(move(comp));
}
}
}
}
}
int num_bcc = (int)bccs.size();
int n_bct = N + num_bcc;
// ── Step 2: Build BCT ────────────────────────────────────────────────────
// Vertex nodes 0..N-1, block nodes N..N+B-1
vector<vector<int>> bct(n_bct);
for (int b = 0; b < num_bcc; b++) {
int bn = N + b;
for (int v : bccs[b]) {
bct[v-1].push_back(bn);
bct[bn].push_back(v-1);
}
}
// ── Step 3: DFS on BCT ───────────────────────────────────────────────────
vector<ll> sub(n_bct, 0);
vector<int> par_bct(n_bct, -1);
vector<int> comp_id(n_bct, -1);
vector<ll> comp_sz;
vector<int> order;
order.reserve(n_bct);
for (int root = 0; root < n_bct; root++) {
if (comp_id[root] != -1 || bct[root].empty()) continue;
int cid = (int)comp_sz.size();
ll csz = 0;
stack<int> dfs;
dfs.push(root);
comp_id[root] = cid;
while (!dfs.empty()) {
int u = dfs.top(); dfs.pop();
order.push_back(u);
if (u < N) csz++;
for (int v : bct[u])
if (comp_id[v] == -1) {
comp_id[v] = cid;
par_bct[v] = u;
dfs.push(v);
}
}
comp_sz.push_back(csz);
}
for (int i = (int)order.size()-1; i >= 0; i--) {
int u = order[i];
sub[u] = (u < N) ? 1 : 0;
for (int v : bct[u])
if (v != par_bct[u])
sub[u] += sub[v];
}
// ── Step 4: Precompute per-block aggregates ───────────────────────────────
// For each block node B:
// child_sum2[B] = sum over child vertex nodes v of B: sub[v]*(sub[v]-1)
// up_sz[B] = comp_sz - sub[B] (size going "upward" through B)
// up_contrib[B] = up_sz * (up_sz - 1) (only valid if B has a parent)
vector<ll> child_sum2(n_bct, 0);
vector<ll> up_contrib(n_bct, 0);
for (int B = N; B < n_bct; B++) {
ll cid = comp_id[B];
ll up_sz = comp_sz[cid] - sub[B];
up_contrib[B] = (par_bct[B] != -1) ? up_sz * (up_sz - 1) : 0;
for (int v : bct[B])
if (par_bct[v] == B) // v is a child of B
child_sum2[B] += sub[v] * (sub[v] - 1);
}
// ── Step 5: Sum contributions ─────────────────────────────────────────────
ll ans = 0;
for (int c = 0; c < N; c++) {
if (bct[c].empty()) continue;
ll S = comp_sz[comp_id[c]] - 1;
if (S < 2) continue;
ll invalid = 0;
for (int B : bct[c]) {
if (par_bct[B] == c) {
// Case A: c is the PARENT of block B in BCT
// All vertices of B other than c are children of B → use child_sum2
// But child_sum2[B] includes sub[?]*(sub[?]-1) for all children of B.
// None of B's children is c (c is B's parent, not child).
// So invalid_from_B = child_sum2[B] (no need to subtract c)
invalid += child_sum2[B];
} else {
// Case B: c is a CHILD of block B in BCT (par_bct[c] == B)
// Grandchildren through B (for c) are:
// - Other children of B (v != c): contribute sub[v]*(sub[v]-1)
// = child_sum2[B] - sub[c]*(sub[c]-1)
// - The upward direction (through par_bct[B]): contribute up_contrib[B]
invalid += child_sum2[B] - sub[c] * (sub[c] - 1) + up_contrib[B];
}
}
ans += S * (S - 1) - invalid;
}
cout << ans << "\n";
return 0;
}