暴力+網路流 [2020牛客暑期多校訓練營(第十場)Identical Trees]
阿新 • • 發佈:2020-08-12
暴力+網路流 2020牛客暑期多校訓練營(第十場)Identical Trees
題目大意:
給你兩棵大小是 \(n\) 的樹,可以對 \(T1\) 進行操作,變成 \(T2\) ,每次操作可以選擇一個節點,把這個節點改成任意一個你想換成的點,問最少的操作使得 \(T1\) 變成 \(T2\) 。
題解:
一個非常暴力的網路流,甚至不需要樹雜湊來判斷是否同構,可以在網路流的過程中直接判斷是否同構。
遞迴,同時列舉兩棵樹的兒子節點,如果是同構,那麼直接建邊,每一層跑一次網路流。
這個說不太清楚,主要看程式碼吧。
#include <bits/stdc++.h> #define inf 0x3f3f3f3f #define debug(x) cout<<"debug:"<<#x<<" = "<<x<<endl; using namespace std; typedef long long ll; /* MCMF 最小費用最大流 */ const int INF = 0x3f3f3f3f; const int maxn = 1000 + 10; struct Edge { int u, v, c, f, cost; Edge(int u, int v, int c, int f, int cost):u(u), v(v), c(c), f(f), cost(cost){} }; struct MCMF { vector<Edge>e; vector<int>G[maxn]; int a[maxn];//找增廣路每個點的水流量 int p[maxn];//每次找增廣路反向記錄路徑 int d[maxn];//SPFA演算法的最短路 int inq[maxn];//SPFA演算法是否在佇列中 int n, m; void init(int s1,int s2) { e.clear(); for(int i = 0; i <= s1+s2+1; i++)G[i].clear(); int s = s1+s2,t = s1+s2+1; for(int i=0;i<s1;i++) addEdge(s,i,1,0); for(int i=s1;i<s1+s2;i++) addEdge(i,t,1,0); } void addEdge(int u, int v, int c, int cost) { e.push_back(Edge(u, v, c, 0, cost)); e.push_back(Edge(v, u, 0, 0, -cost)); int m = e.size(); G[u].push_back(m - 2); G[v].push_back(m - 1); } bool bellman(int s, int t, int& flow, long long & cost) { for(int i = 0; i <= t + 1; i++) d[i] = INF,inq[i] = 0; d[s] = 0;inq[s] = 1;//源點s的距離設為0,標記入隊 p[s] = 0;a[s] = INF;//源點流量為INF(和之前的最大流演算法是一樣的) queue<int>q;//Bellman演算法和增廣路演算法同步進行,沿著最短路拓展增廣路,得出的解一定是最小費用最大流 q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); inq[u] = 0;//入佇列標記刪除 for(int i = 0; i < G[u].size(); i++) { Edge & now = e[G[u][i]]; int v = now.v; if(now.c > now.f && d[v] > d[u] + now.cost) //now.c > now.f表示這條路還未流滿(和最大流一樣) //d[v] > d[u] + e.cost Bellman 演算法中邊的鬆弛 { d[v] = d[u] + now.cost;//Bellman 演算法邊的鬆弛 p[v] = G[u][i];//反向記錄邊的編號 a[v] = min(a[u], now.c - now.f);//到達v點的水量取決於邊剩餘的容量和u點的水量 if(!inq[v]){q.push(v);inq[v] = 1;}//Bellman 演算法入隊 } } } if(d[t] == INF)return false;//找不到增廣路 flow += a[t];//最大流的值,此函式引用flow這個值,最後可以直接求出flow cost += (long long)d[t] * (long long)a[t];//距離乘上到達匯點的流量就是費用 for(int u = t; u != s; u = e[p[u]].u)//逆向存邊 { e[p[u]].f += a[t];//正向邊加上流量 e[p[u] ^ 1].f -= a[t];//反向邊減去流量 (和增廣路演算法一樣) } return true; } int MincostMaxflow(int s, int t, long long & cost) { cost = 0; int flow = 0; while(bellman(s, t, flow, cost));//由於Bellman函式用的是引用,所以只要一直呼叫就可以求出flow和cost return flow;//返回最大流,cost引用可以直接返回最小費用 } }ans; vector<int>G1[maxn]; vector<int> G2[maxn]; int dfs(int rt1,int rt2){ int s1 = G1[rt1].size(),s2 =G2[rt2].size(); if(s1!=s2) return -1; vector<int>e[3]; e[0].clear(),e[1].clear(),e[2].clear(); for(int i=0;i<s1;i++){ for(int j=0;j<s2;j++){ int x = dfs(G1[rt1][i],G2[rt2][j]); if(x!=-1) e[0].push_back(i),e[1].push_back(j+s1),e[2].push_back(x); } } ans.init(s1,s2); ll cost = 0; for(int i=0;i<e[0].size();i++) ans.addEdge(e[0][i],e[1][i],1,e[2][i]); int num = ans.MincostMaxflow(s1+s2,s1+s2+1,cost); // printf("s1=%d s2=%d rt1=%d rt2=%d\n",s1,s2,rt1,rt2); // printf("rt1=%d rt2=%d s1=%d s2=%d num=%d cost=%lld\n",rt1,rt2,s1,s2,num,cost); if(num!=s1) return -1; if(rt1!=rt2) cost++; // printf("cost=%lld\n",cost); return cost; } int main(){ int n; scanf("%d",&n); for(int i=1,x;i<=n;i++) scanf("%d",&x),G1[x].push_back(i); for(int i=1,x;i<=n;i++) scanf("%d",&x),G2[x].push_back(i); ll ans = dfs(G1[0][0],G2[0][0]); printf("%lld\n", ans); return 0; }