题目

题目大意

给你一棵树,树上每个节点有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. MFC-按行读取TXT数据

    TXT中数据格式如下: 1 23 4 0 4 10 …… 要实现的功能是:定义一个函数,每次调用时从TXT文档中读一个整数 ,赋值给变量.同时,文件位置向下移动一行,以便下次调用时读取下一行的数据. ...

  2. 基于Netty的RPC架构学习笔记(十):自定义数据包协议

    文章目录 数据包简介 粘包.分包现象 数据包格式 举个

  3. HDU1556-Color the ball-前缀和/线段树/树状数组

    N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色.但 ...

  4. 网络安全系列 之 TLS/SSL基本原理

    1. TLS/SSL基本工作方式: TLS/SSL的功能实现主要依赖于三类基本算法(参见"网络安全系列 之 密码算法"): 非对称加密算法:实现身份认证和密钥协商 对称加密算法: ...

  5. tcp_tw_recycle和tcp_timestamps的一些知识(转)

    现在很多公司都用LVS做负载均衡,通常是前面一台LVS,后面多台后端服务器,这其实就是NAT,当请求到达LVS后,它修改地址数据后便转发给后端服务器,但不会修改时间戳数据,对于后端服务器来说,请求的源 ...

  6. Redis事务,持久化,哨兵机制

    1 Redis事务 基本事务指令 Redis提供了一定的事务支持,可以保证一组操作原子执行不被打断,但是如果执行中出现错误,事务不能回滚,Redis未提供回滚支持. multi 开启事务 exec 执 ...

  7. PAT L2-019. 悄悄关注 /// map set

    https://www.patest.cn/contests/gplt/L2-019 题目大意: 新浪微博上有个“悄悄关注”,一个用户悄悄关注的人,不出现在这个用户的关注列表上,但系统会推送其悄悄关注 ...

  8. Linux(Centos7)安装ngnix服务器

    Ngnix服务器是一款优秀的静态页服务器软件和反向代理服务器软件 目前,centos安装ngnix可以yum安装也可以下载安装,我们为了扩展方便,选择下载安装.yum一键安装没什么好说的. 一.安装编 ...

  9. HTML语法检测

    网络上收集到的资源: HTML在线编辑//////////////////////////////////////////http://tool.oschina.net/codeformat/ 文本框 ...

  10. 2018-2-13-win10-uwp-上传Nuget-让别人用我们的库

    title author date CreateTime categories win10 uwp 上传Nuget 让别人用我们的库 lindexi 2018-2-13 17:23:3 +0800 2 ...