牛客多校2020 第十场 Identical Trees
神奇的树形DP+二分图最大权值匹配转移
dp[x][y]表示以左边的树x为根,右边的树y为根,他们有dp[x][y]个序号是重合的,若x和y不同构那就dp[x][y] = -INF;
如何转移?
给x的儿子们和y的儿子们建个二分图跑最大权值匹配,跑下来的最大权值就是儿子们的答案,很好笑,但是复杂度有点玄学不敢写罢了,
本人代码弱智,易于理解,详细的看代码把,
#include<iostream> #include<vector> #include<algorithm> #include<cstring> #include<unordered_map> #include<queue> #include<string> using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const int maxn = 3020; int n, m; queue<int> que; int head[maxn]; unordered_map<string, int>id; struct Edge { int id, nex; int w, f; }edge[40400]; int cnt = 0; void init() { cnt = 0; memset(head, -1, sizeof(head)); while (que.size()) que.pop(); } void add_(int x, int y, int f, int w) { edge[cnt].id = y; edge[cnt].f = f; edge[cnt].w = w; edge[cnt].nex = head[x]; head[x] = cnt++; } void add(int x, int y, int f, int w) { add_(x, y, f, w); add_(y, x, 0, -w); } int vis[maxn]; int dis[maxn]; int mflow[maxn]; int per[maxn]; int spfa(int s, int t) { memset(vis, 0, sizeof(vis)); memset(dis, inf, sizeof(dis)); mflow[s] = inf; que.push(s); dis[s] = 0; vis[s] = 1; while (que.size()) { int x = que.front(); que.pop(); vis[x] = 0; for (int i = head[x]; ~i; i = edge[i].nex) { int v = edge[i].id; if (dis[v] > dis[x] + edge[i].w && edge[i].f) { dis[v] = dis[x] + edge[i].w; per[v] = i; mflow[v] = min(mflow[x], edge[i].f); if (vis[v]) continue; vis[v] = 1; que.push(v); } } } if (dis[t] != inf) return 1; else return 0; } void update(int s, int t, int& flow) { int minn = mflow[t]; for (int i = t; i != s; i = edge[per[i] ^ 1].id) { int x = per[i]; edge[x].f -= minn; edge[x ^ 1].f += minn; } flow += minn; } int solve(int s, int t, int& flow) { int ans = 0; while (spfa(s, t)) {//用spfa开路 ans += dis[t] * mflow[t]; update(s, t, flow); } return ans; } ll dp[550][550]; vector<int>G1[maxn], G2[maxn]; int jude[550][550]; int aa = 0; int cnt1[550], cnt2[550]; int dfs(int x, int y) { //看看x和y是否同构 if (jude[x][y] == 0) { dp[x][y] = -inf; return 0; } if (x == y) { dp[x][y] = 1; if (G1[x].size() == 0) return 0; } for (int i = 0; i < G1[x].size(); i++) { for (int j = 0; j < G2[y].size(); j++) { int a = G1[x][i]; int b = G2[y][j]; dfs(a, b); } } init(); for (int i = 0; i < G1[x].size(); i++) add(2010, i, 1, 0); for (int j = 0; j < G2[y].size(); j++) add(j + 505, 2011, 1, 0); for (int i = 0; i < G1[x].size(); i++) { for (int j = 0; j < G2[y].size(); j++) { int a = G1[x][i]; int b = G2[y][j]; add(i, j + 505, 1, -dp[a][b]); } } int cns = 0; int ans = solve(2010, 2011, cns); dp[x][y] -= ans; //cout << x << "----------" << y << ":\t" << dp[x][y] <<"\t"<< cns << endl; return 0; } int get(int x, int y) { for (int i = 0; i < G1[x].size(); i++) { for (int j = 0; j < G2[y].size(); j++) { int a = G1[x][i]; int b = G2[y][j]; get(a, b); } } string sn; if (G1[x].size() == G2[y].size()) { if (G1[x].size() == 0) { jude[x][y] = 1; } else { for (int p : G1[x]) { sn += (char)(cnt1[p] + '0'); } sort(sn.begin(), sn.end()); if (id[sn] == 0) id[sn] = ++aa; cnt1[x] = id[sn]; sn.clear(); for (int p : G2[y]) { sn += (char)(cnt2[p] + '0'); } sort(sn.begin(), sn.end()); if (id[sn] == 0) id[sn] = ++aa; cnt2[y] = id[sn]; sn.clear(); if (cnt1[x] == cnt2[y]) jude[x][y] = 1; } } return 0; } int main() { scanf("%d", &n); int root1, root2; int x; for (int i = 1; i <= n; i++) { scanf("%d", &x); if (x == 0) root1 = i; else G1[x].push_back(i); } for (int i = 1; i <= n; i++) { scanf("%d", &x); if (x == 0) root2 = i; else G2[x].push_back(i); } for (int i = 1; i <= n; i++) dp[i][i] = 1; get(root1, root2); dfs(root1, root2); cout << n - dp[root1][root2] << endl; } /* 7 0 1 1 2 2 3 3 0 1 1 3 3 2 2 输出2 */
寻找真正的热爱