J. Identical Trees
题意
给定两颗有根树,每次操作可以将第一颗树的一个点的编号更换成任意数
求出将第一颗树变成与第二棵树完全一样的最小操作数(只需每个节点的父亲节点编号一致即可)
1 ≤ n ≤ 500 1 \leq n \leq 500 1≤n≤500
思路
定义 d p [ u ] [ v ] dp[u][v] dp[u][v] 为:将 T 1 T1 T1 中的 u u u 点代表的子树,改为 T 2 T2 T2 中的 v v v 点代表的子树的最小操作数,如果 u , v u,v u,v 子树不同构,则为无穷大
那么我们可以从
T
1
,
T
2
T1,T2
T1,T2 的根节点开始,枚举所有的
O
(
n
2
)
O(n ^ 2)
O(n2) 的可行的组合
对于当前的节点
u
1
∈
T
1
,
u
2
∈
T
2
u_1 \in T1, u_2 \in T2
u1∈T1,u2∈T2,他们的所有儿子:
v
1
∈
s
o
n
(
u
1
)
,
v
2
∈
s
o
n
(
u
2
)
v_1 \in son(u_1), v_2 \in son(u_2)
v1∈son(u1),v2∈son(u2)
那么我们就需要为每一个
v
1
v_1
v1 选择一个
v
2
v2
v2,转移过去,花费为:
d
p
[
v
1
]
[
v
2
]
dp[v_1][v_2]
dp[v1][v2]
而且每一个
v
2
v_2
v2 都恰好有一个
v
1
v_1
v1 与其对应,我们就可以将
v
1
,
v
2
v_1, v_2
v1,v2 抽象成点,
从
v
1
v_1
v1 向每一个
v
2
v_2
v2 连边,边的容量为
1
1
1,花费为
d
p
[
v
1
]
[
v
2
]
dp[v_1][v_2]
dp[v1][v2],
那么
d
p
[
u
1
]
[
u
2
]
dp[u_1][u_2]
dp[u1][u2] 就是 最小费用最大流
由于这张图本质上是一个二分图,所以也可以跑一个二分图最大权值匹配 K M KM KM 算法来求,都是可以的。
判同构的话用树哈希即可
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
struct MCF {
struct Edge {
int v, c, w;
Edge(int v, int c, int w) : v(v), c(c), w(w) {}
};
const int n;
std::vector<Edge> e;
std::vector<std::vector<int>> g;
std::vector<ll> h, dis;
std::vector<int> pre;
bool dijkstra(int s, int t) {
dis.assign(n + 1, std::numeric_limits<ll>::max());
pre.assign(n + 1, -1);
std::priority_queue<std::pair<ll, int>, std::vector<std::pair<ll, int>>, std::greater<std::pair<ll, int>>> que;
dis[s] = 0;
que.emplace(0, s);
while (!que.empty()) {
ll d = que.top().first;
int u = que.top().second;
que.pop();
if (dis[u] < d) continue;
for (int i : g[u]) {
int v = e[i].v;
int c = e[i].c;
int w = e[i].w;
if (c > 0 && dis[v] > d + h[u] - h[v] + w) {
dis[v] = d + h[u] - h[v] + w;
pre[v] = i;
que.emplace(dis[v], v);
}
}
}
return dis[t] != std::numeric_limits<ll>::max();
}
MCF(int n) : n(n), g(n + 1) {}
void addEdge(int u, int v, int c, int w) {
g[u].push_back(e.size());
e.emplace_back(v, c, w);
g[v].push_back(e.size());
e.emplace_back(u, 0, -w);
}
std::pair<int, ll> flow(int s, int t) {
int flow = 0;
ll cost = 0;
h.assign(n + 1, 0);
while (dijkstra(s, t)) {
for (int i = 1; i <= n; ++i) h[i] += dis[i];
int aug = std::numeric_limits<int>::max();
for (int i = t; i != s; i = e[pre[i] ^ 1].v) aug = std::min(aug, e[pre[i]].c);
for (int i = t; i != s; i = e[pre[i] ^ 1].v) {
e[pre[i]].c -= aug;
e[pre[i] ^ 1].c += aug;
}
flow += aug;
cost += ll(aug) * h[t];
}
return std::make_pair(flow, cost);
}
};
const int N = 505;
std::vector<int> g[N], f[N];
int dp[N][N];
int rt1, rt2;
int n;
ull hash1[N], hash2[N];
const ull mask = std::chrono::steady_clock::now().time_since_epoch().count();
ull shift(ull x) {
x ^= mask;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x ^= mask;
return x;
}
void getHash(int u, int fa, int id){
std::vector<int>& e = (id == 1 ? g[u] : f[u]);
ull* hash = (id == 1 ? hash1 : hash2);
hash[u] = 1;
for(auto v : e)
if(v ^ fa){
getHash(v, u, id);
hash[u] += shift(hash[v]);
}
}
int solve(int u1, int u2){
if(hash1[u1] != hash2[u2] || g[u1].size() != f[u2].size()) return n + 5; //不同构
if(g[u1].size() + f[u2].size() == 0) return u1 != u2;
for(auto v1 : g[u1])
for(auto v2 : f[u2]){
dp[v1][v2] = solve(v1, v2); //树形DP
}
MCF mcf(2 * g[u1].size() + 5); //两颗树的儿子节点数量
fore(i, 0, g[u1].size()){
int v1 = g[u1][i];
fore(j, 0, f[u2].size()){
int v2 = f[u2][j];
// 流量为1,费用是dp值
if(dp[v1][v2] < n) mcf.addEdge(i + 1, g[u1].size() + j + 1, 1, dp[v1][v2]);
}
}
int S = g[u1].size() * 2 + 1;
int T = S + 1; //源点汇点
fore(i, 0, g[u1].size()) mcf.addEdge(S, i + 1, 1, 0);
fore(i, 0, f[u2].size()) mcf.addEdge(g[u1].size() + i + 1, T, 1, 0);
return mcf.flow(S, T).se + (u1 != u2); //返回费用
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
std::cin >> n;
fore(i, 1, n + 1)
fore(j, 1, n + 1)
dp[i][j] = n + 10000;
fore(i, 1, n + 1){
int fa;
std::cin >> fa;
if(!fa) rt1 = i;
else g[fa].push_back(i);
}
fore(i, 1, n + 1){
int fa;
std::cin >> fa;
if(!fa) rt2 = i;
else f[fa].push_back(i);
}
/* 树哈希 */
getHash(rt1, 0, 1);
getHash(rt2, 0, 2);
std::cout << solve(rt1, rt2);
return 0;
}