#include<bits/stdc++.h>
#define int long long
using namespace std;
vector<int> ad[100005];
int low[100005], tin[100005], dfsc = 0;
bool visited[100005], joint[100005];
void dfs(int u, int par)
{
visited[u] = 1; low[u] = tin[u] = ++dfsc;
int child = 0;
for(int v : ad[u]) if(v != par){
if(visited[v] == 0){
child++; dfs(v, u); low[u] = min(low[u], low[v]);
}
else low[u] = min(low[u], tin[v]);
}
if((par == -1 && child >= 2) || (par > -1 && child >= 1 && low[u] == tin[u])) joint[u] = 1;
}
vector<int> adtr[100005];
int color[100005], resz[100005], sz[100005], num_joint[100005], ans = 0;
bool marked[100005], joint_node[100005];
void dfs_sz(int u)
{
marked[u] = 1; visited[u] = 1;
for(int v : adtr[u]) if(visited[v] == 0){
dfs_sz(v); sz[u] += sz[v];
}
visited[u] = 0;
}
void dfs_solve(int u)
{
visited[u] = 1;
//We're fixing the cc contain the middle
//First case: all three node in the same cc
ans += resz[u] * (resz[u] - 1) * (resz[u] - 2);
//Second case: Two node in the same cc, and one node in another
for(int v : adtr[u]){
ans += resz[u] * (resz[u]-1) * sz[v] * 2;
}
//Last case: one node in this cc, two node in two branches
int sum = 0;
for(int v : adtr[u]){
ans += sum * sz[v] * 2;
sum += sz[v];
}
//Sometimes they could be in the same brances
if(joint_node[u] == 1){
for(int v : adtr[u]) if(joint_node[v] == 0){
int num = sz[v] + num_joint[v] - 1;
ans += num*(num-1);
}
}
//Reroot
for(int v : adtr[u]) if(visited[v] == 0){
int rev = sz[v], reu = sz[u];
sz[v] = sz[u] - sz[v]; swap(sz[u], sz[v]);
dfs_solve(v);
sz[u] = reu; sz[v] = rev;
}
visited[u] = 0;
}
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int n, m; cin>>n>>m;
for(int i = 1; i <= m; i++){
int u, v; cin>>u>>v;
ad[u].push_back(v); ad[v].push_back(u);
}
for(int i = 1; i <= n; i++) if(visited[i] == 0) dfs(i, -1); //More than 1
//Bfs
int cc = 0;
memset(visited, 0, sizeof(visited));
for(int i = 1; i <= n; i++) if(visited[i] == 0){
if(joint[i] == 1){
color[i] = ++cc; sz[cc] = 1; visited[i] = joint_node[cc] = 1; continue;
}
queue<int> w; w.push(i); cc++; visited[i] = 1;
while(w.size() > 0){
int u = w.front(); w.pop(); color[u] = cc; sz[cc]++;
for(int v : ad[u]) if(joint[v] == 0 && visited[v] == 0){
visited[v] = 1; w.push(v);
}
}
}
for(int i = 1; i <= cc; i++) resz[i] = sz[i];
//Compress
vector<pair<int, int>> edges;
for(int u = 1; u <= n; u++){
for(int v : ad[u]) if(color[u] < color[v]){
edges.push_back({color[u], color[v]});
}
}
sort(edges.begin(), edges.end());
edges.erase(unique(edges.begin(), edges.end()), edges.end());
for(pair<int, int> i : edges){
int u = i.first, v = i.second;
adtr[u].push_back(v); adtr[v].push_back(u);
}
for(int u = 1; u <= n; u++) if(joint_node[u] == 0){
for(int v : adtr[u]) if(joint_node[v] == 1) num_joint[u]++;
}
//Now solve
memset(visited, 0, sizeof(visited));
for(int i = 1; i <= cc; i++) if(marked[i] == 0){
dfs_sz(i); dfs_solve(i);
}
cout<<ans;
}