Problem:
Problem I: よくわかる二重魔法
紀慧暦2119 年15 の月。宮廷魔術士Sengemon Lugene の手によって、一冊の魔術書が書き記された。この魔術書「In Magiray」は、この世界の魔術法則の根幹に深く関わる「二重魔法」を、誰にでも習得可能な技術として体系化した、という点で革新的な書物であった。まずは、この書物の内容を要約したものを見ていこう。
【エレメント】
世界中のあらゆる物体は、「泉」「風」「羽」「花」「砂」「灯」……といった、極小要素「エレメント」から構成されている。エレメントには多くの種類があり、人間はエレメントと契約を交わすことで、契約を交わした種類のエレメントを用いた二重魔法を発動させることができる。
【二重魔法】
二重魔法とは、その名の通り、２つのエレメントを重ね合わせて解き放つ魔法である。主となるエレ
メントを「主属性」、もう一方を「副属性」と呼ぶ。たとえば、主属性が「灯」で副属性が「花」な
ら、その二重魔法は（灯，花）と表記される。（灯，花）と（花，灯）は別の二重魔法である。また、主属性と副属性は同じであってもよい。
【キーワード】
「泉→ huda」「風→ loar」「羽→ sheza」「花→ leide」といったように、各々のエレメントに、それぞれ短い「キーワード」を１対１対応させることができる。キーワードは、エレメントの持つ性質を短い言葉に象徴させることで、いくつかの連続するキーワード列を唱えるだけで簡単に二重魔法を発動させることを可能にした、画期的な概念である。
【呪文】
「huda・leide・loar・sheza」のように、１つ以上のキーワードを連続して並べたものを「呪文」という。術者が意図した呪文を唱えることで、対応する二重魔法を発動させることができる。具体的には、呪文の一番最初のキーワードに対応するエレメントを主属性、一番最後のキーワードに対応するエレメントを副属性とする二重魔法が発動する。先ほどの呪文の例だと、発動する二重魔法は（泉，羽）である。なお、呪文が１つのキーワードだけから成る場合には、主属性も副属性も、そのキーワードに対応するエレメントになる。
【エレメント同士の相性】
「huda・leide・loar・sheza」の呪文で（泉，羽）の二重魔法が発動することはすでに述べた。しかし、一見すると、（泉，羽）を使いたいのならば、わざわざ長い呪文を唱えずとも「huda・sheza」だけで良いようにも思える。だが実際には、それは不可能である。なぜなら、「泉（huda）」と「羽（sheza）」は相性が良くないため、これら２つが隣り合った呪文を唱えると、反発作用が起こってエレメント同士が相殺してしまうからである。一方で、「泉（huda）」と「花（leide）」、「花（leide）」と「風（loar）」、「風（loar）」と「羽（sheza）」は、どのペアも好相性なので、「huda・leide・loar・sheza」や「loar・leide・huda」といった呪文では、エレメントの相殺は起こらない。
【呪文論理】
二重魔法を使おうとする者は、あらかじめ、すべての好相性なエレメントのペアそれぞれに対して、
どちらのキーワードが「上位ワード」で、どちらのキーワードが「下位ワード」なのかを定義しておかなければならない。好相性な２つのエレメントのキーワード「
A
」と「
B
」に対して、
A
を上位ワード、
B
を下位ワードと定義したとき、
A
>
B
と表記する。
A
>
B
であるとき、一つの呪文内で、
B
の直後に
A
を並べることはできない。つまり、「
A
1
・
A
2
・
A
3
・……・
A
k
」が正しい呪文であるならば、
A
1
>
A
2
，
A
2
>
A
3
，……，
A
k
-1
>
A
k
でなくてはならない。ちなみに、
A
>
B
かつ
B
>
C
であっても、
A
>
C
である必要はない。
このように、各々の術者が、上位・下位ワードを自分好みに定義することを「呪文論理の構築」と呼
ぶ。これは、あえて呪文の構文に制約をもたせることで、エレメントの流れを明確化し、目的の二重
魔法の成功確率を上げるための工夫である。だが、その代償として、呪文論理を構築すると、どのよ
うに呪文を唱えても発動させることのできない二重魔法が生じてしまう可能性がある（ただし、いか
なる呪文論理を構築しても使うことのできない二重魔法は存在しないことが証明されている）。この
ような事情があるため、どのような呪文論理を構築するかは、今も昔も多くの魔術士を悩ませている
厄介な問題なのである。
とある小さな町で暮らす見習い魔術士の少年ユートは、すべてのエレメントとの契約を終え、いよい
よ明日、自分の呪文論理を構築するための儀式を行おうとしている。ユートは単純に、できるだけた
くさんの種類の二重魔法を発動させることができるような呪文論理が良い呪文論理である、と考えて
いた。
超一級電術技師（現代日本で言うところのスーパープログラマー）であるあなたは、友達のユートか
ら、うまく呪文論理を構築することができれば、最大で何種類の二重魔法を使えるようになるのか知
りたいと相談を受けた。これは、今まで世界中の人間が挑戦してきたが、誰も解くことができなかっ
たほどの難問である。もしも解くことができれば、あなたの名前は未来永劫、世界中で語り継がれる
ことになるであろう。
Input
N M
s
1
s
2
.
.
.
s
N
p
1
q
1
p
2
q
2
.
.
.
p
M
M
入力の１行目には、整数
N
と整数
M
が、空白区切りで書かれている。これは、エレメントが全部で
N
種類、好相性なエレメントのペアが全部で
M
組存在することをあらわす。
続く
N
行には、１つの文字列が書かれている。１＋ i 行目に書かれた文字列
s
i
は、i 番目のエレメントに対応するキーワードが
s
i
であることをあらわす。
続くM 行には、２つの文字列が空白区切りで書かれている。１＋
N
＋ i 行目に書かれた文字列
p
i
と
q
i
は、キーワード
p
i
に対応するエレメントと、キーワード
q
i
に対応するエレメントのペアが、好相性であることをあらわす。逆に、ここにあらわれなかったエレメントのペアは、好相性ではない。
これらのデータは、以下の条件をみたす。
2 ≤
N
≤ 100
1 ≤
M
≤ 4,950
s
i
は、アルファベットの大文字または小文字のみからなる、1 文字以上15 文字以下の文字列
i
≠
j
⇒
s
i
≠
s
j
p
i
≤
q
i
i
≤
j
⇒　(
p
i
,
q
i
) ≠ (
p
j
,
q
j
) かつ(
p
i
,
q
i
) ≠ (
q
j
,
p
j
)
いかなる呪文論理を構築しても発動することのできない二重魔法は存在しない
Output
呪文論理を構築したとき、使用可能になる二重魔法の種類の最大数を出力せよ。
Sample Input 1
4 3
huda
leide
loar
sheza
huda leide
leide 
...(truncated)

Submitted solutions:

code_snippet: #include <iostream>
#include <vector>
using namespace std;

class UnionFind {
public:
	vector<int> par;

	void init(int sz) {
		par.resize(sz, -1);
	}

	int root(int pos) {
		if (par[pos] == -1) return pos;
		par[pos] = root(par[pos]);
		return par[pos];
	}

	void unite(int u, int v) {
		u = root(u); v = root(v);
		if (u == v) return;
		par[u] = v;
	}

	bool same(int u, int v) {
		if (root(u) == root(v)) return true;
		return false;
	}
};

// Variables
int N; string S[10009];
int M; string A[10009], B[10009];
int NA[10009];
int NB[10009];
int Color[10009], ColorCnt;
int W[10009];
bool IsBridge[10009];
vector<int> G[10009];

// Variables for Tree DP
vector<int> H[10009];
int sz[109];
int dp1[109][109][109];
int dp2[109][109][109];

// DFS
void dfs(int pos) {
	Color[pos] = ColorCnt;
	W[ColorCnt] += 1;
	for (int to : G[pos]) {
		if (Color[to] != 0) continue;
		dfs(to);
	}
}

// Tree DP
void dfs2(int pos) {
	sz[pos] = W[pos];
	vector<int> Child;
	for (int to : H[pos]) {
		if (sz[to] >= 1) continue;
		Child.push_back(to);
		dfs2(to);
		sz[pos] += sz[to];
	}

	// Merge DP : Initialize
	for (int i = 0; i <= Child.size(); i++) {
		for (int j = 0; j <= sz[pos]; j++) {
			for (int k = 0; k <= sz[pos]; k++) dp2[i][j][k] = -(1 << 20);
		}
	}
	dp2[0][0][0] = 0;

	// Merge DP : Main
	int sum = 0;
	for (int i = 0; i < Child.size(); i++) {
		for (int j = 0; j <= sum; j++) {
			for (int k = 0; k <= sum; k++) {
				if (dp2[i][j][k] == -(1 << 20)) continue;
				int idx = Child[i];
				for (int j2 = 0; j2 <= sz[idx]; j2++) {
					for (int k2 = 0; k2 <= sz[idx]; k2++) {
						if (dp1[idx][j2][k2] == -(1 << 20)) continue;

						// Down Direction
						int cost1 = j * (k2 + W[idx]);
						int cost2 = W[pos] * (k2 + W[idx]);
						dp2[i + 1][j][k + k2 + W[idx]] = max(dp2[i + 1][j][k + k2 + W[idx]], dp2[i][j][k] + dp1[idx][j2][k2] + cost1 + cost2);

						// Up Direction
						int cost3 = k * (j2 + W[idx]);
						int cost4 = W[pos] * (j2 + W[idx]);
						dp2[i + 1][j + j2 + W[idx]][k] = max(dp2[i + 1][j + j2 + W[idx]][k], dp2[i][j][k] + dp1[idx][j2][k2] + cost3 + cost4);
					}
				}
			}
		}
		sum += sz[Child[i]];
	}

	// Update DP1
	for (int i = 0; i <= sum; i++) {
		for (int j = 0; j <= sum; j++) dp1[pos][i][j] = max(dp1[pos][i][j], dp2[Child.size()][i][j]);
	}
}

int main() {
	// Step 1. Input
	cin >> N >> M;
	for (int i = 1; i <= N; i++) cin >> S[i];
	for (int i = 1; i <= M; i++) cin >> A[i] >> B[i];

	// Step 2. Make Graph
	for (int i = 1; i <= M; i++) {
		for (int j = 1; j <= N; j++) {
			if (S[j] == A[i]) NA[i] = j;
			if (S[j] == B[i]) NB[i] = j;
		}
	}

	// Step 3. Enumerate Bridges
	for (int i = 1; i <= M; i++) {
		UnionFind UF2; UF2.init(N + 2);
		int cnt2 = 0;
		for (int j = 1; j <= M; j++) {
			if (j == i) continue;
			if (UF2.same(NA[j], NB[j]) == true) continue;
			cnt2 += 1;
			UF2.unite(NA[j], NB[j]);
		}
		if (cnt2 != N - 1) IsBridge[i] = true;
		if (cnt2 == N - 1) {
			G[NA[i]].push_back(NB[i]);
			G[NB[i]].push_back(NA[i]);
		}
	}

	// Step 4. Connected Components
	for (int i = 1; i <= N; i++) {
		if (Color[i] != 0) continue;
		ColorCnt += 1;
		dfs(i);
	}

	// Step 5. Make Tree
	for (int i = 1; i <= M; i++) {
		if (IsBridge[i] == false) continue;
		int idx1 = Color[NA[i]];
		int idx2 = Color[NB[i]];
		H[idx1].push_back(idx2);
		H[idx2].push_back(idx1);
	}
	
	// Step 6. Tree DP
	for (int i = 1; i <= N; i++) {
		for (int j = 0; j <= N; j++) {
			for (int k = 0; k <= N; k++) dp1[i][j][k] = -(1 << 20);
		}
	}
	dfs2(1);

	// Step 7. Output
	int Answer = 0;
	for (int i = 0; i <= N; i++) {
		for (int j = 0; j <= N; j++) Answer = max(Answer, dp1[1][i][j]);
	}
	for (int i = 1; i <= ColorCnt; i++) Answer += W[i] * W[i];
	cout << Answer << endl;
	return 0;
}
accuracy: 1
time_ms: 70
memory_kb: 14892
final_rank: 6

code_snippet: #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> l_l;
typedef pair<int, int> i_i;
template<class T>
inline bool chmax(T &a, T b) {
    if(a < b) {
        a = b;
        return true;
    }
    return false;
}

template<class T>
inline bool chmin(T &a, T b) {
    if(a > b) {
        a = b;
        return true;
    }
    return false;
}

const long double EPS = 1e-10;
const long long INF = 1e18;
const long double PI = acos(-1.0L);
typedef vector<vector<int>> UnWeightedGraph;
template< typename G >
struct LowLink {
  const G &g;
  vector< int > used, ord, low;
  vector< int > articulation;
  vector< pair< int, int > > bridge;
 
  LowLink(const G &g) : g(g) {}
 
  int dfs(int idx, int k, int par) {
    used[idx] = true;
    ord[idx] = k++;
    low[idx] = ord[idx];
    bool is_articulation = false;
    int cnt = 0;
    for(auto &to : g[idx]) {
      if(!used[to]) {
        ++cnt;
        k = dfs(to, k, idx);
        low[idx] = min(low[idx], low[to]);
        is_articulation |= ~par && low[to] >= ord[idx];
        if(ord[idx] < low[to]) bridge.emplace_back(minmax(idx, (int) to));
      } else if(to != par) {
        low[idx] = min(low[idx], ord[to]);
      }
    }
    is_articulation |= par == -1 && cnt > 1;
    if(is_articulation) articulation.push_back(idx);
    return k;
  }
 
  virtual void build() {
    used.assign(g.size(), 0);
    ord.assign(g.size(), 0);
    low.assign(g.size(), 0);
    int k = 0;
    for(int i = 0; i < g.size(); i++) {
      if(!used[i]) k = dfs(i, k, -1);
    }
  }
};
template< typename G >
struct TwoEdgeConnectedComponents : LowLink< G > {
  using LL = LowLink< G >;
  vector< int > comp;
 
  TwoEdgeConnectedComponents(const G &g) : LL(g) {}
 
  int operator[](const int &k) {
    return comp[k];
  }
 
  void dfs(int idx, int par, int &k) {
    if(~par && this->ord[par] >= this->low[idx]) comp[idx] = comp[par];
    else comp[idx] = k++;
    for(auto &to : this->g[idx]) {
      if(comp[to] == -1) dfs(to, idx, k);
    }
  }
 
  void build(UnWeightedGraph &t) {
    LL::build();
    comp.assign(this->g.size(), -1);
    int k = 0;
    for(int i = 0; i < comp.size(); i++) {
      if(comp[i] == -1) dfs(i, -1, k);
    }
    t.resize(k);
    for(auto &e : this->bridge) {
      int x = comp[e.first], y = comp[e.second];
      t[x].push_back(y);
      t[y].push_back(x);
    }
  }
};
//const ll mod = 1000000007;
map<string, int> inv;
vector<ll> sz;
ll dp[101][101][101];
ll field[101][101];
int n;
UnWeightedGraph g, t;

void dfs(int now, int from) {
    //cerr << "dfs: " << now << endl;
    for(int i = 0; i <= 100; i++) {
        for(int j = 0; j <= 100; j++) {
            dp[now][i][j] = -INF;
        }
    }
    dp[now][0][0] = 0;
    for(auto to : t[now]) {
        if(to == from) continue;
        dfs(to, now);
        for(int i = 0; i <= 100; i++) {
            for(int j = 0; j <= 100; j++) {
                field[i][j] = -INF;
            }
        }
        for(int delta = 0; delta <= 100; delta++) {
            ll maxi = -INF;
            for(int i = 0; i <= 100; i++) {
                chmax(maxi, dp[to][delta][i]);
            }
            if(maxi < 0) continue;
            for(int i = 0; i + delta <= 100; i++) {
                for(int j = 0; j <= 100; j++) {
                    chmax(field[i+delta][j], dp[now][i][j]+maxi);
                }
            }
        }
        for(int delta = 0; delta <= 100; delta++) {
            ll maxi = -INF;
            for(int i = 0; i <= 100; i++) {
                chmax(maxi, dp[to][i][delta]);
            }
            if(maxi < 0) continue;
            for(int i = 0; i + delta <= 100; i++) {
                for(int j = 0; j <= 100; j++) {
                    chmax(field[j][i+delta], dp[now][j][i]+maxi);
                }
            }
        }
        swap(dp[now], field);
    }
    for(ll i = 0; i <= 100; i++) {
        for(ll j = 0; j <= 100; j++) {
            dp[now][i][j] += i * j + sz[now]*i + sz[now]*j;
        }
    }
    for(int i = 100; i >= 0; i--) {
        for(int j = 100; j >= 0; j--) {
            if(i >= sz[now] and j >= sz[now]) {
                dp[now][i][j] = dp[now][i-sz[now]][j-sz[now]];
            } else {
                dp[now][i][j] = -INF;
            }
        }
    }
    /*
    for(int i = 0; i <= 100; i++) {
        for(int j = 0; j <= 100; j++) {
            if(dp[now][i][j] < 0) continue;
            cerr << now << " " << i << " " << j << " " << dp[now][i][j] << endl;
        }
    }
    */
}

int main() {
    int N, M;
    cin >> N >> M;
    g.resize(N);
    for(int i = 0; i < N; i++) {
        string s;
        cin >> s;
        inv[s] = i;
    }
    for(int m = 0; m < M; m++) {
        string a, b;
        cin >> a >> b;
        int A = inv[a];
        int B = inv[b];
        g[A].push_back(B);
        g[B].push_back(A);
    }
    TwoEdgeConnectedComponents<UnWeightedGraph> TECC(g);
    TECC.build(t);
    n = t.size();
    sz.resize(n);
    for(int i = 0; i < N; i++) {
        sz[TECC[i]]++;
    }
    ll base = 0;
    for(int i = 0; i < t.size(); i++) {
        base += sz[i] * sz[i];
        /*
        cerr << "--" << i << "--" << sz[i] << "---" << endl;
        for(auto to : t[i]) cerr << to << " ";
        cerr << endl;
        */
    }
    //cerr << base << endl;
    dfs(0, -1);
    ll ans = base;
    for(int i = 0; i <= 100; i++) {
        for(int j = 0; j <= 100; j++) {
            if(dp[0][i][j] < 0) continue;
            //cerr << i << " " << j << " " << dp[0][i][j] << endl;
            chmax(ans, base + dp[0][i][j]);
        }
    }
    cout << ans << endl;
    return 0;
}
accuracy: 1
time_ms: 40
memory_kb: 11280
final_rank: 5

code_snippet: #include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
class DisjointSet {
	// Union-Find Tree
	int sz;
	vector<int> par;
public:
	DisjointSet() : sz(0), par(vector<int>()) {};
	DisjointSet(int sz_) : sz(sz_) {
		par = vector<int>(sz_);
		for (int i = 0; i < sz_; ++i) {
			par[i] = i;
		}
	};
	int root(int x) {
		if (x == par[x]) return x;
		return par[x] = root(par[x]);
	}
	void unite(int x, int y) {
		par[root(x)] = root(y);
	}
	bool connected(int x, int y) {
		return root(x) == root(y);
	}
};
int medival(vector<int> v) {
	// Let X the nearest subset sum to half, returns X * (sum - X)
	int sum = 0;
	for (int i = 0; i < v.size(); ++i) {
		sum += v[i];
	}
	vector<bool> vis(sum + 1, false);
	vis[0] = true;
	for (int i = 0; i < v.size(); ++i) {
		for (int j = sum; j >= v[i]; --j) {
			if (vis[j - v[i]]) {
				vis[j] = true;
			}
		}
	}
	for (int i = sum / 2; i >= 0; --i) {
		if (vis[i]) return i * (sum - i);
	}
	return -1;
}
int solve(int N, vector<int> a, vector<int> b) {
	// The graph is connected
	int M = a.size();
	DisjointSet all(N);
	vector<bool> bridge(M, false);
	vector<int> va, vb;
	for (int i = 0; i < M; ++i) {
		DisjointSet d(N);
		for (int j = 0; j < M; ++j) {
			if (i != j) {
				d.unite(a[j], b[j]);
			}
		}
		for (int j = 1; j < N; ++j) {
			if (!d.connected(0, j)) bridge[i] = true;
		}
		if (!bridge[i]) {
			all.unite(a[i], b[i]);
		}
		else {
			va.push_back(a[i]);
			vb.push_back(b[i]);
		}
	}
	vector<int> vli;
	for (int i = 0; i < N; ++i) {
		vli.push_back(all.root(i));
	}
	sort(vli.begin(), vli.end());
	vli.erase(unique(vli.begin(), vli.end()), vli.end());
	vector<int> weight(vli.size());
	vector<vector<int> > tree(vli.size());
	for (int i = 0; i < va.size(); ++i) {
		va[i] = find(vli.begin(), vli.end(), all.root(va[i])) - vli.begin();
		vb[i] = find(vli.begin(), vli.end(), all.root(vb[i])) - vli.begin();
		tree[va[i]].push_back(vb[i]);
		tree[vb[i]].push_back(va[i]);
	}
	for (int i = 0; i < N; ++i) {
		++weight[find(vli.begin(), vli.end(), all.root(i)) - vli.begin()];
	}
	function<pair<int, int>(int, int)> calc = [&](int pos, int pre) {
		int sum = 0, ord = 0;
		for (int i : tree[pos]) {
			if (i == pre) continue;
			pair<int, int> res = calc(i, pos);
			ord += res.second;
			sum += res.first - res.second * res.second + weight[pos] * res.second;
		}
		return make_pair(ord * ord + sum, ord + weight[pos]);
	};
	int ret = 0;
	for (int i = 0; i < vli.size(); ++i) {
		int sum = weight[i] * weight[i]; // <-- CENTER
		vector<int> sz;
		for (int j : tree[i]) {
			pair<int, int> res = calc(j, i);
			sum += res.second * res.second - res.first + res.second * weight[i];
			sz.push_back(res.second);
		}
		sum += medival(sz);
		ret = max(ret, sum);
	}
	return ret;
}
int main() {
	cin.tie(0);
	ios_base::sync_with_stdio(false);
	int N, M;
	cin >> N >> M;
	vector<string> s(N);
	for (int i = 0; i < N; ++i) {
		cin >> s[i];
	}
	vector<int> va(M), vb(M);
	for (int i = 0; i < M; ++i) {
		string sa, sb;
		cin >> sa >> sb;
		va[i] = find(s.begin(), s.end(), sa) - s.begin();
		vb[i] = find(s.begin(), s.end(), sb) - s.begin();
	}
	DisjointSet d(N);
	for (int i = 0; i < M; ++i) {
		d.unite(va[i], vb[i]);
	}
	int ret = 0;
	for (int i = 0; i < N; ++i) {
		vector<int> vli;
		for (int j = 0; j < N; ++j) {
			if (d.root(j) == i) {
				vli.push_back(j);
			}
		}
		if (vli.empty()) continue;
		vector<int> ca, cb;
		for (int j = 0; j < M; ++j) {
			if (d.root(va[j]) == i) {
				ca.push_back(find(vli.begin(), vli.end(), va[j]) - vli.begin());
				cb.push_back(find(vli.begin(), vli.end(), vb[j]) - vli.begin());
			}
		}
		ret += solve(vli.size(), ca, cb);
	}
	cout << ret << '\n';
	return 0;
}
accuracy: 1
time_ms: 70
memory_kb: 3360
final_rank: 3

code_snippet: #include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i=0;i<(n);++i)
#define rep1(i,n) for(int i=1;i<=(n);++i)
#define all(c) (c).begin(),(c).end()
#define fs first
#define sc second
#define pb push_back
#define show(x) cout << #x << " " << x << endl
#define chmax(x,y) x=max(x,y)
//biedge connected component
//bs???bridge,cc???????????£??????????????\???
typedef vector<int> vi;
typedef pair<int,int> P;
const int MAX_V=100;
vector<int> G[MAX_V];
int ord[MAX_V];
bool inS[MAX_V];
stack<int> roots,S;
vector<vi> cc;
vector<P> bs;
int cnt;
int N,M;
int cmp[MAX_V];
vector<int> cG[MAX_V];
int n[MAX_V];
void vis(int v,int p){
	ord[v]=++cnt;
	S.push(v);
	inS[v]=true;
	roots.push(v);
	for(int u:G[v]){
		if(ord[u]==0){
			vis(u,v);
		}else if(u!=p&&inS[u]){
			while(ord[roots.top()]>ord[u]) roots.pop();
		}
	}
	if(v==roots.top()){
		bs.pb(P(p,v));
		vector<int> vc;
		while(true){
			int w=S.top();S.pop();
			inS[w]=false;
			vc.pb(w);
			cmp[w]=cc.size();
			if(v==w) break;
		}
		roots.pop();
		n[cc.size()]=vc.size();
		cc.pb(vc);
	}
}
void bridge(){
	rep(i,N) if(ord[i]==0){
		vis(i,-1);
		bs.pop_back();	//P(-1,hoge)
	}
	for(P p:bs){
		int x=cmp[p.fs],y=cmp[p.sc];
		cG[x].pb(y),cG[y].pb(x);
	}
}
map<string,int> msi;
/*int rec(int v,int p,int x,int y,int e){
	if(x<0||y<0) return -inf;
	int& val=dp[v][x][y][e]=-inf;
	if(val>=0) return val;
	if(e==0){
		if(x==0&&y==0) return val=0;
		assert(false);
	}
	int w=G[v][e-1];
	if(w==p) return val=rec(v,p,x,y,e-1);
	rep(i,sz[w]+1){
		chmax(val,rec(v,p,x-i,y,e-1)+u[w][i]);
		chmax(val,rec(v,p,x,y-i,e-1)+d[w][i]);
	}
	if(e==G[v].size()){
		chmax(u[v][x],val+x*y);
		chmax(d[v][y],val+x*y);
	}
	return val;
}*/
int sz[MAX_V];
int u[MAX_V][MAX_V+1],d[MAX_V][MAX_V+1];
int t[MAX_V+1][MAX_V+1],nt[MAX_V+1][MAX_V+1];
int inf=1e8;
void dfs(int v,int p){
	for(int w:cG[v]) if(w!=p) dfs(w,v);
	rep(i,101) rep(j,101) t[i][j]=nt[i][j]=-inf;
	t[0][0]=0;
	for(int w:cG[v]){
		if(w==p) continue;
		rep(i,sz[v]+1) rep(j,sz[v]+1){
			rep(k,sz[w]+1){
				chmax(nt[i+k][j],t[i][j]+u[w][k]);
				chmax(nt[i][j+k],t[i][j]+d[w][k]);
			}
		}
		sz[v]+=sz[w];
		rep(i,100+1) rep(j,100+1) t[i][j]=nt[i][j],nt[i][j]=-inf;
	}
	rep(i,sz[v]+1) rep(j,sz[v]+1){
		chmax(u[v][i+n[v]],t[i][j]+(i+n[v])*(j+n[v]));
		chmax(d[v][j+n[v]],t[i][j]+(i+n[v])*(j+n[v]));
	}
/*	show(v);
	show(sz[v]);
	show(sz[v]+n[v]);
	rep(i,sz[v]+n[v]+1){
		printf("u[%d][%d]=%d\n",v,i,u[v][i]);
		printf("d[%d][%d]=%d\n",v,i,d[v][i]);
	}*/
	sz[v]+=n[v];
}
int main(){
	cin>>N>>M;
	rep(i,N){
		string s;
		cin>>s;
		msi[s]=i;
	}
	rep(i,M){
		string s,t;
		cin>>s>>t;
		int x=msi[s],y=msi[t];
		G[x].pb(y);
		G[y].pb(x);
	}
	bridge();
	dfs(0,-1);
	int ans=0;
	rep(i,N+1) chmax(ans,max(u[0][i],d[0][i]));
	cout<<ans<<endl;
}
accuracy: 1
time_ms: 10
memory_kb: 1436
final_rank: 1

code_snippet: #include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <climits>
#include <ctime>
#include <queue>
#include <stack>
#include <algorithm>
#include <list>
#include <vector>
#include <set>
#include <map>
#include <iostream>
#include <deque>
#include <complex>
#include <string>
#include <iomanip>
#include <sstream>
#include <bitset>
#include <valarray>
#include <unordered_map>
#include <iterator>
#include <assert.h>
using namespace std;
typedef long long int ll;
typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;

#define REP(i,x) for(int i=0;i<(int)(x);i++)
#define REPS(i,x) for(int i=1;i<=(int)(x);i++)
#define RREP(i,x) for(int i=((int)(x)-1);i>=0;i--)
#define RREPS(i,x) for(int i=((int)(x));i>0;i--)
#define FOR(i,c) for(__typeof((c).begin())i=(c).begin();i!=(c).end();i++)
#define RFOR(i,c) for(__typeof((c).rbegin())i=(c).rbegin();i!=(c).rend();i++)
#define ALL(container) (container).begin(), (container).end()
#define RALL(container) (container).rbegin(), (container).rend()
#define SZ(container) ((int)container.size())
#define mp(a,b) make_pair(a, b)
#define UNIQUE(v) v.erase( unique(v.begin(), v.end()), v.end() );

template<class T> bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T> bool chmin(T &a, const T &b) { if (a>b) { a=b; return 1; } return 0; }
template<class T> ostream& operator<<(ostream &os, const vector<T> &t) {
os<<"["; FOR(it,t) {if(it!=t.begin()) os<<","; os<<*it;} os<<"]"; return os;
}
template<class T> ostream& operator<<(ostream &os, const set<T> &t) {
os<<"{"; FOR(it,t) {if(it!=t.begin()) os<<","; os<<*it;} os<<"}"; return os;
}
template<class S, class T> ostream& operator<<(ostream &os, const pair<S,T> &t) { return os<<"("<<t.first<<","<<t.second<<")";}
template<class S, class T> pair<S,T> operator+(const pair<S,T> &s, const pair<S,T> &t){ return pair<S,T>(s.first+t.first, s.second+t.second);}
template<class S, class T> pair<S,T> operator-(const pair<S,T> &s, const pair<S,T> &t){ return pair<S,T>(s.first-t.first, s.second-t.second);}

const int INF = 1<<28;
const double EPS = 1e-8;
const int MOD = 1000000007;

struct UnionFind {
  vector<int> data;
  UnionFind(int size=0) : data(size, -1) { }
  bool unionSet(int x, int y) {
    x = root(x); y = root(y);
    if (x != y) {
      if (data[y] < data[x]) swap(x, y);
      data[x] += data[y]; data[y] = x;
    }
    return x != y;
  }
  bool findSet(int x, int y) {
    return root(x) == root(y);
  }
  int root(int x) {
    return data[x] < 0 ? x : data[x] = root(data[x]);
  }
  int size(int x) {
    return -data[root(x)];
  }
};

int n, m;
UnionFind uf;

vector<vi> g;
int size[2001][2001];
int memo[2001][2001];
int rec(int r, int p){
	if(p != -1 && memo[r][p] != 0) return memo[r][p];
	int rnum = uf.size(r);
	if(p == -1){
		vi childs;
		int res = rnum*rnum;
		FOR(v, g[r])if(*v != p){
			res += rec(*v, r);
			res += size[*v][r]*rnum;
			childs.push_back(size[*v][r]);
		}
		vi dp(2001);
		dp[0] = 1;
		REP(i, childs.size()){
			RREP(j, 2001-childs[i]){
				dp[j+childs[i]] |= dp[j];
			}
		}
		int mm = 0;
		REP(i, n-rnum){
			if(dp[i]) mm = max(mm, i*(n-rnum-i));
		}
		return res + mm;
	}else{
		memo[r][p] = rnum*rnum;
		size[r][p] = rnum;
		FOR(v, g[r])if(*v != p){
			memo[r][p] += rec(*v, r);
			memo[r][p] += rnum*size[*v][r];
			size[r][p] += size[*v][r];
		}
		return memo[r][p];
	}
}
int BCCdfs(int r, int p, vector<vi> &g, vi &steps, vi &visited, UnionFind &uf, int step){
	if(visited[r] >= 1) return steps[r];
	steps[r] = step;
	visited[r] ++;
	int minstep = INF;
	FOR(v, g[r])if(*v!=p){
		int ret = BCCdfs(*v, r, g, steps, visited, uf, step+1);
		if(ret <= step) uf.unionSet(r, *v);
		chmin(minstep, ret);
	}
	return minstep;
}

UnionFind BCC(vector<vi> &g){
	vi steps(g.size());
	vi visited(g.size());
	uf = UnionFind(g.size());
	REP(i, g.size()) if(!visited[i]) BCCdfs(i, -1, g, steps, visited, uf, 0);
	return uf;
}

main(){
	cin >> n >> m;
	map<string, int> to;
	REP(i, n){
		string s;
		cin >> s;
		to[s] = i;
	}
	g = vector<vi>(n);
	REP(i, m){
		string u, v;
		cin >> u >> v;
		g[to[u]].push_back(to[v]);
		g[to[v]].push_back(to[u]);
	}
	UnionFind uf = BCC(g);
	vector<vi> g2(n);
	REP(i, n){
		REP(j, g[i].size()){
			if(uf.root(i) != uf.root(g[i][j])){
				g2[uf.root(i)].push_back(uf.root(g[i][j]));
				g2[uf.root(g[i][j])].push_back(uf.root(i));
			}
		}
	}
	REP(i, n){
		sort(ALL(g2[i]));
		UNIQUE(g2[i]);
	}
	g = g2;
	int ans = 0;
	REP(i, n) if(uf.root(i) == i) ans = max(ans, rec(i, -1));
	cout << ans << endl;
	return 0;
}
accuracy: 1
time_ms: 10
memory_kb: 5784
final_rank: 2

code_snippet: #include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <math.h>
#include <assert.h>
#include <vector>
#include <queue>
#include <string>
#include <map>
#include <set>

using namespace std;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
static const double EPS = 1e-9;
static const double PI = acos(-1.0);

#define REP(i, n) for (int i = 0; i < (int)(n); i++)
#define FOR(i, s, n) for (int i = (s); i < (int)(n); i++)
#define FOREQ(i, s, n) for (int i = (s); i <= (int)(n); i++)
#define FORIT(it, c) for (__typeof((c).begin())it = (c).begin(); it != (c).end(); it++)
#define MEMSET(v, h) memset((v), h, sizeof(v))

typedef int Weight;
struct Edge {
  int src;
  int dest;
  Weight weight;
  Edge(int src, int dest, Weight weight) : src(src), dest(dest), weight(weight) {;}
  bool operator<(const Edge &rhs) const {
    if (src != rhs.src) { return src < rhs.src; }
    return dest < rhs.dest;
  }
};
typedef vector<Edge> Edges;
typedef vector<Edges> Graph;
typedef vector<Weight> Array;
typedef vector<Array> Matrix;

void PrintMatrix(const Matrix &matrix) {
  for (int y = 0; y < (int)matrix.size(); y++) {
    for (int x = 0; x < (int)matrix[y].size(); x++) {
      printf("%d ", matrix[y][x]);
    }
    puts("");
  }
}


void BridgeDfs(
    const Graph &g,
    int from,
    int parent,
    vector<Edge> &bridge,
    vector<vector<int> > &connect,
    vector<int> &roots,
    vector<int> &st,
    vector<int> &order,
    int &cnt) {
  order[from] = cnt++;
  st.push_back(from);
  roots.push_back(from);
  for (Edges::const_iterator it = g[from].begin(); it != g[from].end(); it++) {
    int to = it->dest;
    if (to == parent) { continue; }
    if (order[to] == -1) {
      BridgeDfs(g, to, from, bridge, connect, roots, st, order, cnt);
    } else {
      while (order[roots.back()] > order[to]) { roots.pop_back(); }
    }
  }
  if (from == roots.back()) {
    bridge.push_back(Edge(parent, from, 0));
    connect.push_back(vector<int>());
    while (true) {
      int w = st.back();
      st.pop_back();
      connect.back().push_back(w);
      if (from == w) { break; }
    }
    roots.pop_back();
  }
}

Edges Bridge(const Graph &g) {
  const int n = g.size();
  vector<Edge> bridge;
  vector<int> order(n, -1);
  vector<int> roots, st;
  vector<vector<int> > connect;
  int cnt = 0;
  for (int i = 0; i < n; i++) {
    if (order[i] != -1) { continue; }
    BridgeDfs(g, i, i, bridge, connect, roots, st, order, cnt);
    bridge.pop_back();
  }
  return bridge;
}

int BridgeCompressDfs(const Graph &g, int from, int id, map<int, int> &mapto, set<Edge> &ban) {
  mapto[from] = id;
  int ret = 1;
  for (Edges::const_iterator it = g[from].begin(); it != g[from].end(); it++) {
    if (mapto.count(it->dest) || ban.count(*it)) { continue; }
    ret += BridgeCompressDfs(g, it->dest, id, mapto, ban);
  }
  return ret;
}

int dp[110][110][110];
Graph BridgeCompress(const Graph &g) {
  const int n = g.size();
  Edges bridge = Bridge(g);
  set<Edge> ban;
  for (Edges::const_iterator it = bridge.begin(); it != bridge.end(); it++) {
    ban.insert(*it);
    ban.insert(Edge(it->dest, it->src, it->weight));
  }
  int m = 0;
  map<int, int> mapto;
  REP(i, n) {
    if (mapto.count(i)) { continue; }
    int w = BridgeCompressDfs(g, i, m, mapto, ban);
    dp[m][w][w] = w * w;
    dp[m][w][0] = w * w;
    dp[m][0][w] = w * w;
    m++;
  }
  Graph ret(m);
  for (set<Edge>::iterator it = ban.begin(); it != ban.end(); it++) {
    ret[mapto[it->src]].push_back(Edge(mapto[it->src], mapto[it->dest], it->weight));
  }
  return ret;
}

Graph g;
int n;
char str1[10000];
char str2[10000];

int dfs(int from, int parent) {
  FORIT(it, g[from]) {
    int to = it->dest;
    if (to == parent) { continue; }
    dfs(to, from);
    for (int l = n; l >= 1; l--) {
      for (int r = n; r >= 1; r--) {
        if (dp[from][l][r] == -1) { continue; }
        FOREQ(i, 1, n) {
          if (dp[to][i][0] != -1) {
            int nl = l + i;
            int nans = dp[from][l][r] + dp[to][i][0] + i * r;
            dp[from][nl][r] = max(dp[from][nl][r], nans);
            dp[from][nl][0] = max(dp[from][nl][0], nans);
            dp[from][0][r] = max(dp[from][0][r], nans);
          }
          if (dp[to][0][i] != -1) {
            int nr = r + i;
            int nans = dp[from][l][r] + dp[to][0][i] + i * l;
            dp[from][l][nr] = max(dp[from][l][nr], nans);
            dp[from][0][nr] = max(dp[from][0][nr], nans);
            dp[from][l][0] = max(dp[from][l][0], nans);
          }
        }
      }
    }
  }
  int ret = 0;
  FOREQ(i, 1, n) {
    ret = max(ret, dp[from][0][i]);
    ret = max(ret, dp[from][i][0]);
  }
  return ret;
}

int main() {
  int m;
  while (scanf("%d %d", &n, &m) > 0) {
    MEMSET(dp, -1);
    g = Graph(n);
    map<string, int> mapto;
    REP(i, n) {
      scanf("%s", str1);
      mapto[str1] = i;
    }
    REP(i, m) {
      scanf("%s %s", str1, str2);
      int f = mapto[str1];
      int t = mapto[str2];
      g[f].push_back(Edge(f, t, 0));
      g[t].push_back(Edge(t, f, 0));
    }
    g = BridgeCompress(g);
    int ans = dfs(0, 0);
    printf("%d\n", ans);
  }
}
accuracy: 1
time_ms: 60
memory_kb: 6136
final_rank: 4