暴力+网络流 [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
",s1,s2,rt1,rt2);
 	// printf("rt1=%d rt2=%d s1=%d s2=%d num=%d cost=%lld
",rt1,rt2,s1,s2,num,cost);
 	if(num!=s1) return -1; 
 	if(rt1!=rt2) cost++;
 	// printf("cost=%lld
",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
", ans);
 	return 0;
 }


原文地址:https://www.cnblogs.com/EchoZQN/p/13492567.html