1. 程式人生 > 實用技巧 >暴力+網路流 [2020牛客暑期多校訓練營(第十場)Identical Trees]

暴力+網路流 [2020牛客暑期多校訓練營(第十場)Identical Trees]

暴力+網路流 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;
 }