图论——最大匹配
- 匹配:定义一个图 G = ( E , V ) G = (E,V) G=(E,V) ,匹配是指边集的一个子集 V ′ ⊆ V V' \subseteq V V′⊆V。在 V ′ V' V′中,没有任意一个节点 u u u,使得 u u u与 a a a边相连又与 b b b边相连( a ≠ b a \neq b a=b)。
- 极大匹配:在一个匹配 M M M中,再也不能添加一个边 a a a使得 M ∪ { a } M \cup \{a\} M∪{a}也是一个 G G G的匹配。
- 最大匹配:在所有极大匹配中,边集数量最大的那个就是最大匹配,最大匹配可能不唯一。
树的最大匹配
使用树形DP的方式解决即可。
struct Wint : vector<int>
{
Wint() { clear(); }
Wint(int x)
{
clear();
while (x)
push_back(x % 10), x /= 10;
}
void operator~()
{
for (int i = 0; i + 1 < size(); i++)
(*this)[i + 1] += (*this)[i] / 10, (*this)[i] %= 10;
while (!empty() && back() > 9)
{
int tmp = back() / 10;
back() %= 10;
push_back(tmp);
}
while (!empty() && !back())
pop_back();
}
void operator+=(const Wint &x)
{
// print(),putchar('+'),x.print();
if (size() < x.size())
resize(x.size());
for (int i = 0; i < x.size(); i++)
(*this)[i] += x[i];
~*this;
// putchar('='),print(),putchar('\n');
}
friend Wint operator*(const Wint &x, const Wint &y)
{
// x.print(),putchar('*'),y.print();
Wint z;
if (!x.size() || !y.size())
return z;
z.resize(x.size() + y.size() - 1);
for (int i = 0; i < x.size(); i++)
for (int j = 0; j < y.size(); j++)
z[i + j] += x[i] * y[j];
~z;
// putchar('='),z.print(),putchar('\n');
return z;
}
void print() const
{
if (empty())
{
putchar('0');
return;
}
for (int i = size() - 1; i >= 0; i--)
putchar('0' + (*this)[i]);
}
};
struct Edge
{
short to;
short nxt;
} e[2005];
short head[1005];
short tot;
void add(int u, int v)
{
tot++;
e[tot].to = v;
e[tot].nxt = head[u];
head[u] = tot;
}
short dp[1005][2]; // 0: 节点不与子节点匹配的最大匹配数 1: 节点与子节点匹配的最大匹配数
Wint num[1005][2][510]; // 0: 节点不与子节点匹配的最大匹配数的方案数 1: 节点与子节点匹配的最大匹配数的方案数
void tdp(int u, int r)
{
short sum = 0;
for (int ne = head[u]; ne; ne = e[ne].nxt)
{
int to = e[ne].to;
if (to == r)
continue;
tdp(to, u);
dp[u][0] += max(dp[to][0], dp[to][1]);
sum += max(dp[to][0], dp[to][1]);
}
for (int ne = head[u]; ne; ne = e[ne].nxt)
{
int to = e[ne].to;
if (to == r)
continue;
dp[u][1] = max(dp[u][1], short(sum - max(dp[to][0], dp[to][1]) + dp[to][0] + 1));
}
}
void ndp(int u, int r)
{
num[u][0][0] = 1;
for (int ne = head[u]; ne; ne = e[ne].nxt)
{
int to = e[ne].to;
if (to == r)
continue;
ndp(to, u);
for (int i = 509; i > 0; i--)
{
// 更新不与子节点匹配的方案
num[u][1][i] +=
(i - dp[to][1] >= 0 ? (dp[to][1] != 0 ? num[to][1][dp[to][1]] : 0) * num[u][1][i - dp[to][1]] : 0);
num[u][1][i] +=
(i - dp[to][0] >= 0 ? (dp[to][0] != 0 ? num[to][0][dp[to][0]] : 0) * num[u][1][i - dp[to][0]] : 0);
// 更新与子节点匹配的方案
num[u][1][i] +=
(i - dp[to][0] - 1 >= 0 ? num[to][0][dp[to][0]] * num[u][0][i - dp[to][0] - 1] : 0);
num[u][0][i] +=
(i - dp[to][1] >= 0 ? num[to][1][dp[to][1]] * num[u][0][i - dp[to][1]] : 0);
num[u][0][i] +=
(i - dp[to][0] >= 0 ? (dp[to][0] != 0 ? num[to][0][dp[to][0]] : 0) * num[u][0][i - dp[to][0]] : 0);
}
}
}
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
int r, c;
cin >> r >> c;
for (int j = 0; j < c; j++)
{
int s;
cin >> s;
add(r, s);
add(s, r);
}
}
tdp(1, 1);
ndp(1, 1);
ll mx = max(dp[1][0], dp[1][1]);
num[1][0][mx] += num[1][1][mx];
cout << mx << endl;
num[1][0][mx].print();
}
int main()
{
FR;
ios::sync_with_stdio(false);
cin.tie(0);
solve();
return 0;
}