题目

题目大意

给你一棵树,树上每个节点有000或111的状态。

用最少的操作次数使得当前状态与目标状态同构。


思考历程

首先想到的是找重心。

因为根是不确定的,但重心只会有一个或两个,以重心为根就能方便很多。

如果重心有两个,就将连接它们的边拆成点,让它们分别与这个点相连就好了(重心是连在一起的)。

然后就是树上哈希……

哈希之后就开始了艰辛的思考历程……

于是比赛就结束了。


正解

我前面的思考是没有问题的。

后面的该怎么处理?当然是DP啊!

设fi,jf_{i,j}fi,j​表示子树iii对应子树jjj的最小操作数。

显然如果iii能匹配jjj,就必须要保证它们的哈希值相等。

但是还会有其它条件,综合起来就是它们祖先的哈希值也相等……

其实为了简化操作,我们再保证它们的深度相等就行了,虽然这并不能保证它们真的能够匹配,但也就算了吧……(我曾试过将完全出去这些冗余状态,但程序跑得更慢了,这意味着数据的这一类冗余状态并不多)

所以可以将点按照深度和哈希值排序,从后往前做。

接下来考虑转移,首先ai xor bja_i \ xor \ b_jai​ xor bj​是一定要加上的、

然后就将各自的子树两两配对,也就是∑fx,y\sum f_{x,y}∑fx,y​,其中xxx是iii的儿子,yyy是jjj的儿子,而且xxx和yyy哈希值相等。

我们要保证这个东西最小。

于是这就变成了二分图的最小权完备匹配。

题目说每个点的度数小于等于111111,这意味着看起来能够状压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\n",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\n",f[root][root]);
return 0;
}

总结

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

[JZOJ3296] 【SDOI2013】刺客信条的更多相关文章

  1. 【BZOJ3197】[SDOI2013]刺客信条

    [BZOJ3197][SDOI2013]刺客信条 题面 bzoj 洛谷 题解 关于树的同构,有一个非常好的性质: 把树的重心抠出来,那么会出现两种情况: 1.有一个重心,那么我们直接把这个重心作为树的 ...

  2. [BZOJ3197][SDOI2013]刺客信条assassin

    bzoj luogu Description 故事发生在1486 年的意大利,Ezio原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一名刺客.最终,凭借着他的努力和出众的 ...

  3. BZOJ3197:[SDOI2013]刺客信条——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=3197 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受 ...

  4. Bzoj3197/洛谷3296 [SDOI2013]刺客信条assassin(树的重心+树Hash+树形DP+KM)

    题面 Bzoj 洛谷 题解 (除了代码均摘自喻队的博客,可是他退役了) 首先固定一棵树,枚举另一棵树,显然另一棵树只有与这棵树同构才有可能产生贡献 如果固定的树以重心为根,那么另一棵树最多就只有重心为 ...

  5. [SDOI2013]刺客信条

    Description 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一名刺客.最终,凭借着他的努力和出众的天赋,成为了杰出的刺 ...

  6. JZOJ 3296 Luogu P3296 [SDOI2013]刺客信条

    前言 做法来自:@pzrpzr ,写一下!Orz pzr! 题目大意 \(n\) 个点的无根树,每个点有两个 \(0/1\) 权值,合适地安排节点在同构树中的顺序,使得前后对应的权值不同节点个数最小, ...

  7. 【JZOJ3296】【SDOI2013】刺客信条(assassin)

    ╰( ̄▽ ̄)╭ Description 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一名刺客.最终,凭借着他的努力和出众的天赋 ...

  8. BZOJ3130: [Sdoi2013]费用流[最大流 实数二分]

    3130: [Sdoi2013]费用流 Time Limit: 10 Sec  Memory Limit: 128 MBSec  Special JudgeSubmit: 960  Solved: 5 ...

  9. Unity实现刺客信条灯光的思路探究

    灯光需求 类似刺客信条的开场CG动画,场景中打着酷炫的灯光,玩家在场景中行走可以感受到灯光很真实. 参考视频:http://www.iqiyi.com/w_19rqytbmvt.html 运行环境 安 ...

随机推荐

  1. 10.RabbitMQ Fanout类型交换机

    Fanout类型交换机忽略Routing Key,它将消息传递到所有与它绑定的队列上.       Producer.java package com.test.fanout;   import co ...

  2. sql中desc的妙用

    很多情况下对表内部结构不熟悉,想看下我创建的表里面又多少表明,又想他按照首字母的规律排列, 这时候就要用desc +表名了. desc t_bike_info; hive> desc t_bik ...

  3. day28-描述符应用与类的装饰器

    #!/usr/bin/env python# -*- coding:utf-8 -*-# ------------------------------------------------------- ...

  4. HttpWebRequest 基础连接已经关闭: 接收时发生错误 GetRequestStream 因为算法不同,客户端和服务器无法通信。

    在代码行 HttpWebRequest objRequest = (HttpWebRequest)HttpWebRequest.Create(sUrl 前面加上 ServicePointManager ...

  5. python语法基础(类)

    一.什么是类? 类是具有相同属性的一类事物 类还有功能和属性,属性就是这类事物的特征,而功能就是它能做什么,也是就是方法或者函数. 在python中类用关键词class来声明 二.类的声明 类的声明方 ...

  6. 关于RSA加密和签名的区别与联系

    发现网上对于RSA加密和签名的介绍普遍偏向于使用和概念的说明,今天想说一点不一样的.对于加解密和签名的使用及概念就不再说了,不知道的请自行百度. 签名的本质其实就是加密,但是由于签名无需还原成明文,因 ...

  7. arm-linux-strip 的使用

    3.2.1    1. 移除所有的符号信息 [arm@localhost gcc]#cp hello hello1 [arm@localhost gcc]#arm­linux­strip ­strip ...

  8. Postgraduate

    https://account.chsi.com.cn/passport/login?entrytype=yzgr&service=https%3A%2F%2Fyz.chsi.com.cn%2 ...

  9. 0918CSP-S模拟测试赛后总结

    14名.110分.可以算是几次大落之后的一次小小的崛起?? 然而sdfz的开挂选手AK了啊…… T2重测前rank7我就高兴地像个傻子??也不看看这次T1是个什么题. 实力还是不行.一眼秒掉了简单题, ...

  10. 大数据之hadoop小文件存档

    hadoop小文件存档1.HDFS存档小文件弊端 每个文件均按块存储,每个块的元数据存储在NameNode的内存中,因此HDFS存储小文件会非常低效.因为大量的小文件会耗尽NameNode中的大部分内 ...