题目

题目大意

给你一棵树,树上每个节点有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. AsyncAwait 学习

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  2. Codeforces Round #526 D - The Fair Nut and the Best Path /// 树上两点间路径花费

    题目大意: 给定一棵树 树上每个点有对应的点权 树上每条边有对应的边权 经过一个点可得到点权 经过一条边必须花费边权 即从u到v 最终得分=u的点权-u到v的边权+v的点权 求树上一条路径使得得分最大 ...

  3. 中文linux安装oracle界面乱码解决方案

    来自:http://blog.csdn.net/h249059945/article/details/12122853 在linux的中文操作系统下使用xmanager进行oracle进行安装的时候, ...

  4. ES6 学习 -- Class

    Class的基本语法(1)// 定义类class Point { test() { console.log("hello test"); }}通过 new 定义好的类即可生成一个类 ...

  5. java异常继承何类,运行时异常与一般异常的区别

    一.基本概念 Throwable是所有异常的根,java.lang.ThrowableError是错误,java.lang.ErrorException是异常,java.lang.Exception ...

  6. Activiti学习笔记7 — ReceiveTask机器自动任务的使用

    一. 创建流程规则,如下图 二.发布流程 /** * 2.发布一个流程 */ @Test public void testDeployProcess() { RepositoryService rep ...

  7. ASP.NET 页面的生命周期

    本文转载自清风飘过的博客,地址:http://www.cnblogs.com/couhujia/archive/2010/04/23/1718405.html 页面生命期分三个阶段:建立阶段,回发阶段 ...

  8. mysql 远程连接报错

    ./mysql -uzhu -p -h192.168.1.200 ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.1.200 ...

  9. redis笔记_源码_双端链表list

    参考:https://redissrc.readthedocs.io/en/latest/datastruct/adlist.html

  10. Let's Encryt免费SSL证书申请[我司方案]

    Let's Encrypt颁发的证书是目前生产的大多数浏览器都信任的,您只需下载并运行Let's Encrypt客户端来生成一个证书即可. 在颁发证书之前,需要验证您的域名的所有权.首先,在您的主机上 ...