title
简化题意:
给定一棵树和两组权值(权值只有 \(0~1\)),求第一组权值最少改变多少个之后这棵树经过重标号之后与第二组权值相同(即树同构)。
analysis
比较神奇的一道题,当我在看到简化题意之前,我是没有准确的理解题意的,说的有些隐晦呵。
不过从中抽出了这之中的真正题意后,一个算法就已经应运而生:树 \(hash\) 判树同构(不会请右转: [BJOI2015]树的同构 。)
然后这是个树形结构,那就要么 \(dfs\) ,要么树形 \(Dp\) 。
因为这道题有明确的的可以设置的状态(其他的两个条件都满足哈),所以选择树形 \(Dp\) 。
状态:设 \(f[i][j]\) 表示 \(树_1\) 的 \(i\) 子树匹配 \(树_2\) 的 \(j\) 子树的最小代价。
状态转移是需要思考的,因为存在很多条件:
- 必要条件是 \(i\) 和 \(j\) 所在子树同构而且二者深度相同;
- 同时还必须保证这两子树对应所有子节点的最小花费都已经计算出来。
然后就是要树 \(hash\) 判树同构, \(hash\) 值相同的节点,而且深度相同,就是可以对应的。
但是因为是无根树,直接任选根 \(hash\) 可能会有问题,因此还要找出重心作根来 \(hash\) (如果有多个就加一个虚点)。
接下来要处理的问题就是最小花费的匹配,这个东西其实就是一个费用流的模型(我就是照着费用流的标签来写的,谁知道这题这题这么神)。
对于可以对应的两个点 \((i,j)\) ,我们就在这两个点之间连一条费用为 \(f[i][j]\) ,流量为 \(1\) 的边即可。
因为树同构,最终的答案就是 \(f[rt][rt]\) 。
有一点注意的就是:因为要对每个点都进行一次同构对应建图,所以建出一张新图后,要在跑费用流之前清空之前的最小费用。另外,会 \(ZKW\) 费用流的,就别用 \(EK\) 了。
code
#include<bits/stdc++.h>
typedef long long ll;
const int maxn=1410,maxm=4e5+10,inf=0x3f3f3f3f,p=233;
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
template<typename T>inline void write(T x,char str)
{
if (!x) *fe++=48;
if (x<0) *fe++='-', x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) *fe++=ch[num--];
*fe++=str;
}
}
using IO::read;
using IO::write;
template<typename T>inline bool chkMin(T &a,const T &b) { return a>b ? (a=b, true) : false; }
template<typename T>inline bool chkMax(T &a,const T &b) { return a<b ? (a=b, true) : false; }
template<typename T>inline T min(T a,T b) { return a<b ? a : b; }
template<typename T>inline T max(T a,T b) { return a>b ? a : b; }
struct Graph
{
int ver[maxm<<1],edge[maxm<<1],Next[maxm<<1],cost[maxm<<1],head[maxn],len;
inline void Clear()
{
memset(head,0,sizeof(head));
len=1;
}
inline void add(int x,int y,int z,int c)
{
ver[++len]=y,edge[len]=z,cost[len]=c,Next[len]=head[x],head[x]=len;
ver[++len]=x,edge[len]=0,cost[len]=-c,Next[len]=head[y],head[y]=len;
}
} G, A;
namespace ZKW
{
int s,t;
int dist[maxn];
bool vis[maxn];
inline bool spfa()
{
memset(dist,0x3f,sizeof(dist));
memset(vis,0,sizeof(vis));
std::queue<int>q;q.push(s);
dist[s]=0, vis[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
vis[x]=0;
for (int i=A.head[x]; i; i=A.Next[i])
{
if (!A.edge[i]) continue;
int y=A.ver[i];
if (dist[y]>dist[x]+A.cost[i])
{
dist[y]=dist[x]+A.cost[i];
if (!vis[y]) q.push(y),vis[y]=1;
}
}
}
if (dist[t]==inf) return false;
else return true;
}
int ans;
inline int get(int x,int low)
{
vis[x]=1;
if (x==t) return low;
int tmp=low;
for (int i=A.head[x]; i; i=A.Next[i])
{
int y=A.ver[i];
if (A.edge[i] && dist[y]==dist[x]+A.cost[i] && (!vis[y] || y==t))
{
int a=get(y,min(tmp,A.edge[i]));
if (a>0)
{
ans+=a*A.cost[i];
A.edge[i]-=a;
A.edge[i^1]+=a;
if (!(tmp-=a)) break;
}
}
}
return low-tmp;
}
inline void NetFlow()
{
while (spfa())
{
vis[t]=1;
while (vis[t])
{
memset(vis,0,sizeof(vis));
get(s,inf);
}
}
}
}
using ZKW::NetFlow;
using ZKW::s;
using ZKW::t;
using ZKW::ans;
int siz[maxn],son[maxn],rt[5];
int root,rtsiz,n;
inline void getroot(int x,int fa)
{
siz[x]=1;
for (int i=G.head[x]; i; i=G.Next[i])
{
int y=G.ver[i];
if (y==fa) continue;
getroot(y,x);
siz[x]+=siz[y];
chkMax(son[x],siz[y]);
}
chkMax(son[x],n-siz[x]);
if (son[root]==son[x]) rt[++rtsiz]=x;
else if (son[root]>son[x]) root=rt[rtsiz=1]=x;
}
int can;
ll hs[maxn],r[maxn];
inline void gethash(int x,int fa)
{
ll sum=0; siz[x]=1;
for (int i=G.head[x]; i; i=G.Next[i])
{
int y=G.ver[i];
if (y==fa || can==i || can==(i^1)) continue;
gethash(y,x);
siz[x]+=siz[y];
sum+=hs[y];
}
hs[x]=(1ll*sum*p)^r[siz[x]];
}
int a[maxn],b[maxn],f[maxn][maxn];
inline void dp(int x,int fa)
{
for (int i=G.head[x]; i; i=G.Next[i])
{
int y=G.ver[i];
if (y==fa || can==i || can==(i^1)) continue;
dp(y,x);
}
for (int u=1; u<=n; ++u)
{
if (hs[u]^hs[x]) continue;
A.Clear();
for (int i=G.head[u]; i; i=G.Next[i])
{
int y=G.ver[i];
A.add(y+n,t,1,0);
}
for (int i=G.head[x]; i; i=G.Next[i])
{
int y=G.ver[i];
if (y==fa || can==i || can==(i^1)) continue;
A.add(s,y,1,0);
for (int k=G.head[u]; k; k=G.Next[k])
{
int v=G.ver[k];
if (hs[y]==hs[v]) A.add(y,v+n,1,f[y][v]);
}
}
ans=0;//记得要清空
NetFlow();
f[x][u]=ans+(a[x]!=b[u]);
}
}
int main()
{
srand(19260817);
read(n); s=0, t=(n<<1)+2;
G.Clear();
A.Clear();
for (int i=1,x,y; i<n; ++i) read(x),read(y),G.add(x,y,0,0);
for (int i=1; i<=n; ++i) read(a[i]);
for (int i=1; i<=n; ++i) read(b[i]);
son[0]=inf;
getroot(1,0);
if (rtsiz==2)
{
for (int i=G.head[rt[1]]; i; i=G.Next[i])
{
int y=G.ver[i];
if (y==rt[2]) { can=i; break; }
}
root=++n;
a[n]=b[n]=0;
G.add(n,rt[1],0,0);
G.add(n,rt[2],0,0);
}
for (int i=1; i<=n; ++i) r[i]=((rand()%32766)<<15ll)+(rand()%32766);
memset(f,0x3f,sizeof(f));
gethash(root,0);
dp(root,0);
write(f[root][root],'\n');
IO::flush();
return 0;
}