[JZOJ3296] 【SDOI2013】刺客信条

题目

题目大意

给你一棵树,树上每个节点有0011的状态。
用最少的操作次数使得当前状态与目标状态同构。


思考历程

首先想到的是找重心。
因为根是不确定的,但重心只会有一个或两个,以重心为根就能方便很多。
如果重心有两个,就将连接它们的边拆成点,让它们分别与这个点相连就好了(重心是连在一起的)。
然后就是树上哈希……
哈希之后就开始了艰辛的思考历程……
于是比赛就结束了。


正解

我前面的思考是没有问题的。
后面的该怎么处理?当然是DP啊!
fi,jf_{i,j}表示子树ii对应子树jj的最小操作数。
显然如果ii能匹配jj,就必须要保证它们的哈希值相等。
但是还会有其它条件,综合起来就是它们祖先的哈希值也相等……
其实为了简化操作,我们再保证它们的深度相等就行了,虽然这并不能保证它们真的能够匹配,但也就算了吧……(我曾试过将完全出去这些冗余状态,但程序跑得更慢了,这意味着数据的这一类冗余状态并不多)
所以可以将点按照深度和哈希值排序,从后往前做。
接下来考虑转移,首先ai xor bja_i xor b_j是一定要加上的、
然后就将各自的子树两两配对,也就是fx,ysum f_{x,y},其中xxii的儿子,yyjj的儿子,而且xxyy哈希值相等。
我们要保证这个东西最小。
于是这就变成了二分图的最小权完备匹配。
题目说每个点的度数小于等于1111,这意味着看起来能够状压DP。
但实际上,如果不非常用力地开常数,那状压DP是很难卡过去的……(我打了80分)。
于是就可以用费用流或KM算法(因为这题求的是最小权,所以将边权取相反数就可以了)。
时间复杂度为玄学……


代码

状压DP


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 710
#define mod 1000000007
inline void get_min(int &a,int b){
	a>b?a=b:0;
}
int n;
int e[N][11],ne[N];
int bz[N][11],nbz;
int siz[N];
int tmpr[2],cnt,root;
void get_siz(int x,int fa){
	bool is_root=1;
	siz[x]=1;
	for (int i=0;i<ne[x];++i){
		int y=e[x][i];
		if (y!=fa){
			get_siz(y,x);
			siz[x]+=siz[y];
			if (y!=fa)
				is_root&=(siz[y]<<1<=n);
		}
	}
	is_root&=(n-siz[x]<<1<=n);
	if (is_root)
		tmpr[cnt++]=x;
}
int fa[N],dep[N];
long long powd[N],h[N];
inline bool cmpe(int x,int y){
	return siz[x]<siz[y] || siz[x]==siz[y] && h[x]<h[y];
}
void get_hash(int x){
	dep[x]=dep[fa[x]]+1;
	siz[x]=1;
	for (int i=0;i<ne[x];++i){
		int y=e[x][i];
		if (y!=fa[x]){
			fa[y]=x;
			get_hash(y);
			siz[x]+=siz[y];
		}
		else{
			for (int j=i;j<ne[x]-1;++j)
				e[x][j]=e[x][j+1];
			ne[x]--;
			--i;
		}
	}
	sort(e[x],e[x]+ne[x],cmpe);
	h[x]=siz[x];
	int w=1;
	for (int i=0;i<ne[x];++i){
		int y=e[x][i];
		h[x]=(h[x]+h[y]*powd[w])%mod;
		w+=siz[y];
	}
}
int q[N];
inline bool cmpq(int x,int y){
	return dep[x]<dep[y] || dep[x]==dep[y] && h[x]<h[y];
}
int f[N][N],g[13][2048];
//int bg[13][13];
int a[N],b[N];
int main(){
	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for (int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		e[u][ne[u]++]=v;
		e[v][ne[v]++]=u;
	}
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for (int i=1;i<=n;++i)
		scanf("%d",&b[i]);
	get_siz(1,0);
	if (cnt==2){
		int u=tmpr[0],v=tmpr[1];
		root=++n;
		for (int i=0;i<ne[u];++i)
			if (e[u][i]==v){
				e[u][i]=root;
				break;
			}
		for (int i=0;i<ne[v];++i)
			if (e[v][i]==u){
				e[v][i]=root;
				break;
			}
		e[root][0]=u,e[root][1]=v,ne[root]=2;
	}
	else
		root=tmpr[0];
	powd[0]=1;
	for (int i=1;i<=n;++i)
		powd[i]=powd[i-1]*997%mod;
	get_hash(root);
	for (int i=1;i<=n;++i)
		q[i]=i;
	sort(q+1,q+n+1,cmpq);
	memset(f,63,sizeof f);
	for (int i=n,r=n,ii=q[i];i>=1;ii=q[--i]){
		for (int j=i,jj=q[j];j>=1 && dep[ii]==dep[jj] && h[ii]==h[jj];jj=q[--j]){
			memset(g,63,sizeof g);
			g[0][0]=0;
			for (int x=0;x<ne[ii];++x){
				int xx=e[ii][x];
				for (int k=0;k<1<<ne[ii];++k)
					for (int y=0;y<ne[jj];++y){
						if (k>>y&1)
							continue;
						get_min(g[x+1][k|1<<y],g[x][k]+f[xx][e[jj][y]]);
					}
			}
			f[ii][jj]=g[ne[ii]][(1<<ne[ii])-1]+(a[ii]^b[jj]);
			if (ii==jj)
				continue;
			memset(g,63,sizeof g);
			g[0][0]=0;
			for (int y=0;y<ne[jj];++y){
				int yy=e[jj][y];
				for (int k=0;k<1<<ne[jj];++k)
					for (int x=0;x<ne[ii];++x){
						if (k>>x&1)
							continue;
						get_min(g[y+1][k|1<<x],g[y][k]+f[yy][e[ii][x]]);
					}
			}
			f[jj][ii]=g[ne[jj]][(1<<ne[jj])-1]+(a[jj]^b[ii]);
		}
	}
	printf("%d
",f[root][root]);
	return 0;
}

KM算法

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 710
#define mod 1000000007
inline void get_min(int &a,int b){
	a>b?a=b:0;
}
int n;
int e[N][11],ne[N];
int bz[N][11],nbz;
int siz[N];
int tmpr[2],cnt,root;
void get_siz(int x,int fa){
	bool is_root=1;
	siz[x]=1;
	for (int i=0;i<ne[x];++i){
		int y=e[x][i];
		if (y!=fa){
			get_siz(y,x);
			siz[x]+=siz[y];
			if (y!=fa)
				is_root&=(siz[y]<<1<=n);
		}
	}
	is_root&=(n-siz[x]<<1<=n);
	if (is_root)
		tmpr[cnt++]=x;
}
int fa[N],dep[N];
long long powd[N],h[N];
inline bool cmpe(int x,int y){
	return siz[x]<siz[y] || siz[x]==siz[y] && h[x]<h[y];
}
void get_hash(int x){
	dep[x]=dep[fa[x]]+1;
	siz[x]=1;
	for (int i=0;i<ne[x];++i){
		int y=e[x][i];
		if (y!=fa[x]){
			fa[y]=x;
			get_hash(y);
			siz[x]+=siz[y];
		}
		else{
			for (int j=i;j<ne[x]-1;++j)
				e[x][j]=e[x][j+1];
			ne[x]--;
			--i;
		}
	}
	sort(e[x],e[x]+ne[x],cmpe);
	h[x]=siz[x];
	int w=1;
	for (int i=0;i<ne[x];++i){
		int y=e[x][i];
		h[x]=(h[x]+h[y]*powd[w])%mod;
		w+=siz[y];
	}
}
int q[N];
inline bool cmpq(int x,int y){
	return dep[x]<dep[y] || dep[x]==dep[y] && h[x]<h[y];
}
int f[N][N];
int m;
int bg[13][13];
int exl[13],exr[13];
bool visl[13],visr[13];
int bel[13];
bool find(int x){
	visl[x]=1;
	for (int i=0;i<m;++i)
		if (exl[x]+exr[i]==bg[x][i] && !visr[i]){
			visr[i]=1;
			if (bel[i]==-1 || find(bel[i])){
				bel[i]=x;
				return 1;
			}
		}
	return 0;
}
inline int km(){
	memset(bel,255,sizeof bel);
	for (int i=0;i<m;++i){
		exl[i]=bg[i][0];
		for (int j=1;j<m;++j)
			exl[i]=max(exl[i],bg[i][j]);
	}
	memset(exr,0,sizeof exr);
	for (int k=0;k<m;++k){
		while (1){
			memset(visl,0,sizeof visl);
			memset(visr,0,sizeof visr);
			if (find(k))
				break;
			int d=0x3f3f3f3f;
			for (int i=0;i<m;++i)
				if (visl[i])
					for (int j=0;j<m;++j)
						if (!visr[j])
							d=min(d,exl[i]+exr[j]-bg[i][j]);
			for (int i=0;i<m;++i)
				if (visl[i])
					exl[i]-=d;
			for (int j=0;j<m;++j)
				if (visr[j])
					exr[j]+=d;
		}
	}
	int res=0;
	for (int i=0;i<m;++i)
		res+=bg[bel[i]][i];
	return -res;
}
int a[N],b[N];
inline void calc(int ii,int jj){
	for (int x=0;x<ne[ii];++x){
		int xx=e[ii][x];
		for (int y=0;y<ne[jj];++y)
			bg[x][y]=-f[xx][e[jj][y]];
	}
	m=ne[ii];
	f[ii][jj]=km()+(a[ii]^b[jj]);
}
int main(){
	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for (int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		e[u][ne[u]++]=v;
		e[v][ne[v]++]=u;
	}
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for (int i=1;i<=n;++i)
		scanf("%d",&b[i]);
	get_siz(1,0);
	if (cnt==2){
		int u=tmpr[0],v=tmpr[1];
		root=++n;
		for (int i=0;i<ne[u];++i)
			if (e[u][i]==v){
				e[u][i]=root;
				break;
			}
		for (int i=0;i<ne[v];++i)
			if (e[v][i]==u){
				e[v][i]=root;
				break;
			}
		e[root][0]=u,e[root][1]=v,ne[root]=2;
	}
	else
		root=tmpr[0];
	powd[0]=1;
	for (int i=1;i<=n;++i)
		powd[i]=powd[i-1]*997%mod;
	get_hash(root);
	for (int i=1;i<=n;++i)
		q[i]=i;
	sort(q+1,q+n+1,cmpq);
	memset(f,63,sizeof f);
	for (int i=n,r=n,ii=q[i];i>=1;ii=q[--i]){
		for (int j=i,jj=q[j];j>=1 && dep[ii]==dep[jj] && h[ii]==h[jj];jj=q[--j]){
			calc(ii,jj);
			if (ii!=jj)
				calc(jj,ii);
		}
	}
	printf("%d
",f[root][root]);
	return 0;
}

总结

想不出来的东西就用网络流吧……

原文地址:https://www.cnblogs.com/jz-597/p/11145215.html