很容易想到建图,初始想法为,建完图后,求一个最小路径覆盖,但因为整个图不是DAG,所以需要缩点,但路径覆盖有两种说法,一种是最小不相交路径覆盖,另一种是最小可相交路径覆盖。
对于最小不相交路径覆盖,我们可以采用求二分图最大匹配来解决,因为每个点最多只有一个前驱节点和后继节点,因此将一个点拆成两个,即可转换为二分图。
对于最小可相交路径覆盖,每个点的前驱节点和后继节点的个数不在保证至多为1,所以我们需要先进行一遍传递闭包,例如a->b->c->d,我们对于中间的不关心,我们只关心最终的结果,即a可以到达d,中间的过程我们不关心,因为可以重复覆盖。由此就转化为了最小不相交路径覆盖问题。
对于建图,我们不能将字母和字母之间直接建图,例如 A B AB AB,我们就建一条从 A A A到 B B B的边,这样是不可行的,有以下反例:ab,bc,ac,如果我们按照上述进行建图,我们最终得到的答案为1,因为我们只需要一个abc的字符串即可,但对于字符串ac,其并没有作为子串进行出现。
所以正确的建图方式为:双重循环遍历,当两个字符串,其中一个开头的字母和另一个结尾的字母相同时,我们就再这两个串之间建一条边即可。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 5;
typedef long long ll;
const int maxv = 4e6 + 5;
typedef pair<ll, ll> pll;
typedef array<int,3> ar;
// #define endl "\n"
int mod=998244353;
constexpr int inf = 1E9;
template<class T>
struct MaxFlow {
struct _Edge {
int to;
T cap;
_Edge(int to, T cap) : to(to), cap(cap) {}
};
int n;
std::vector<_Edge> e;
std::vector<std::vector<int>> g;
std::vector<int> cur, h;
MaxFlow() {}
MaxFlow(int n) {
init(n);
}
void init(int n) {
this->n = n;
e.clear();
g.assign(n, {});
cur.resize(n);
h.resize(n);
}
bool bfs(int s, int t) {
h.assign(n, -1);
std::queue<int> que;
h[s] = 0;
que.push(s);
while (!que.empty()) {
const int u = que.front();
que.pop();
for (int i : g[u]) {
auto [v, c] = e[i];
if (c > 0 && h[v] == -1) {
h[v] = h[u] + 1;
if (v == t) {
return true;
}
que.push(v);
}
}
}
return false;
}
T dfs(int u, int t, T f) {
if (u == t) {
return f;
}
auto r = f;
for (int &i = cur[u]; i < int(g[u].size()); ++i) {
const int j = g[u][i];
auto [v, c] = e[j];
if (c > 0 && h[v] == h[u] + 1) {
auto a = dfs(v, t, std::min(r, c));
e[j].cap -= a;
e[j ^ 1].cap += a;
r -= a;
if (r == 0) {
return f;
}
}
}
return f - r;
}
void addEdge(int u, int v, T c) {
g[u].push_back(e.size());
e.emplace_back(v, c);
g[v].push_back(e.size());
e.emplace_back(u, 0);
}
T flow(int s, int t) {
T ans = 0;
while (bfs(s, t)) {
cur.assign(n, 0);
ans += dfs(s, t, std::numeric_limits<T>::max());
}
return ans;
}
std::vector<bool> minCut() {
std::vector<bool> c(n);
for (int i = 0; i < n; i++) {
c[i] = (h[i] != -1);
}
return c;
}
struct Edge {
int from;
int to;
T cap;
T flow;
};
std::vector<Edge> edges() {
std::vector<Edge> a;
for (int i = 0; i < e.size(); i += 2) {
Edge x;
x.from = e[i + 1].to;
x.to = e[i].to;
x.cap = e[i].cap + e[i + 1].cap;
x.flow = e[i + 1].cap;
a.push_back(x);
}
return a;
}
};
int n, m, tot, dfsn[N], ins[N], low[N];
stack<int> s;
vector<int> e[N];
vector<vector<int>> scc;
vector<int> b(N);
void dfs(int x)
{
low[x] = dfsn[x] = ++tot, ins[x] = 1, s.push(x);
for (auto u : e[x])
{
if (!dfsn[u])
{
dfs(u);
low[x] = min(low[x], low[u]);
}
else if (ins[u])
low[x] = min(low[x], dfsn[u]);
}
if (dfsn[x] == low[x])
{
vector<int> c;
while (1)
{
auto t = s.top();
c.push_back(t);
ins[t] = 0;
s.pop();
b[t] = scc.size();
// z[scc.size()]+=a[t];
if (t == x)
break;
}
scc.push_back(c);
}
}
void add(int u, int v)
{
e[u].push_back(v);
}
MaxFlow<int> mf;
int w[1005][1005];
void solve()
{
cin>>n;
// vector<int> st(30);
string ss[n+5];
for(int i=0;i<n;i++) cin>>ss[i];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (ss[i].back() == ss[j].front())
e[i].push_back(j);
}
}
for(int i=0;i<n;i++){
// if(!st[i]) continue;
if(!dfsn[i]) dfs(i);
}
mf.init(n*n*2+5);
int s=n*n+1,t=n*n+2;
for(int i=0;i<n;i++){
for(auto j: e[i]){
if(b[i]!=b[j]){
w[b[i]][b[j]]=1;
}
}
}
int res=scc.size();
// cout<<res<<endl;
for(int k=0;k<res;k++){
for(int i=0;i<res;i++){
for(int j=0;j<res;j++){
if(i==j) continue;
if(w[i][k]&&w[k][j]) w[i][j]=1;
}
}
}
for(int i=0;i<res;i++){
for(int j=0;j<res;j++){
if(w[i][j]&&i!=j) mf.addEdge(i,j+res,1);
}
mf.addEdge(s,i,1),mf.addEdge(i+res,t,1);
}
cout<<res-mf.flow(s,t)<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin>>t;
while (t--)
{
solve();
}
system("pause");
return 0;
}