题目

题目大意

给你一棵树,树上每个节点有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. ZK4字命令

    zookeeper4字命令:两种方式,1.通过telnet链接服务器,执行stat.2.echo stat|nc xxx.xxx.xxx.xxx 2181效果是一样的conf:zk服务器运行时的基本信 ...

  2. 日常 java+雅思+训练题1

    今天主要学了一些类似c中的一些语句,java也是一样类似的,只有一些点需要稍微注意一下,一些语句是新增的需要知道. 完完全全新学的知识就是class和instance的区别.如何创建实例.数据的封装. ...

  3. POJ 3761 Bubble Sort

    题目链接:https://vjudge.net/problem/POJ-3761 转自:https://blog.csdn.net/cscj2010/article/details/7820906 题 ...

  4. ssrf对redis未授权访问写webshell

    docker建立redis镜像 docker run -d -p 9999:6379 redis 将redis的6379端口映射到物理机的9999端口 使用工具生成攻击代码 攻击 进入容器查看

  5. JS-copy到剪贴板

    因为 clipboard.js 兼容性受限

  6. 2014 0416 word清楚项目黑点 输入矩阵 普通继承和虚继承 函数指针实现多态 强弱类型语言

    1.word 如何清除项目黑点 选中文字区域,选择开始->样式->全部清除 2.公式编辑器输入矩阵 先输入方括号,接着选择格式->中间对齐,然后点下面红色框里的东西,组后输入数据   ...

  7. Spark DataFrame中的join使用说明

    spark sql 中join的类型 Spark DataFrame中join与SQL很像,都有inner join, left join, right join, full join; 类型 说明 ...

  8. fastReport.net 初了解

    delphi 中fastReport rmReport都很好用,转到.net了,第一想法也是这两个,好在这里有个fastReport; 这个安装呢 找个破解的 有个4.x版 安完建一个winForm  ...

  9. python学习3—数据类型之整型、字符串和布尔值

    python学习3-数据类型之整型.字符串和布尔值 数据类型 python3支持的数据类型共有6种: 1 Number 2 String 3 List 4 Tuple 5 Set 6 Dictiona ...

  10. 调用第三方jar包_md5加密

    vars.put是转换成jmeter格式