前言

做法来自:@pzrpzr ,写一下!Orz pzr!

题目大意

\(n\) 个点的无根树,每个点有两个 \(0/1\) 权值,合适地安排节点在同构树中的顺序,使得前后对应的权值不同节点个数最小,并输出。

解题思路

首先,我们套路地将无根树的同构问题转化成以重心为根的有根树的同构问题。(相关证明请查阅相关资料,此处不赘述。)

找出重心,以此为根。若有两个重心,则新增一个点,使其为根,并删去原重心之间的连边,且两个重心分别向根连边。不难发现此节点一定为重心,且不影响答案。

注意到一个条件即一个节点的度数 \(<=11\),这启示我们使用dp解决这个问题。( \(son_x\) 表示 \(x\) 的儿子个数。)

设 \(f_{x,y}\) 表示 \(x\) 子树与 \(y\) 子树同构,\(x\) 对应 \(y\) 时,最小的代价。容易发现 \(x\) 和 \(y\) 的深度、儿子个数、子树大小均应该相等。故真正有用的状态很小。

至于转移,pzr想到不需要通过树hash进行判断同构。如果我们能将 \(x\) 和 \(y\) 的子树分别一一配对,由其转移即可。设一个辅助dp数组 \(g_{i,s}\) 表示 \(x\) 的儿子匹配到第 \(i\) 个,\(y\) 的儿子匹配的状态为 \(s\) 的最小代价。转移可以枚举子集。显然

\[f_{x,y}=g_{son_x,2^{son_y+1}-1}(son_x=son_y)+[val_{1,i} \ne val_{2,j}]
\]

时间复杂度不好分析,恳请大神帮忙算一下。

最后要注意,找到重心以后 \(siz\) , \(son\) 要重新算。因为这个wa了一发。

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define M 13
#define W 1030
#define N 710
#define INF 0x3f3f3f3f
#define ff(i, a, b) for(int i = (a); i < (b); ++i)
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
using namespace std;
inline int read() // negative , long long
{
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return x;
}
int n, p2[M] = {1}, cnt[W], siz[N], fa[N], son[N], c[3], f[N][N], g[M][W];
int last[N], pre[N << 1], to[N << 1];
bool op1[N], op2[N];
inline void add(int u, int v){static int tot = 0; pre[++tot] = last[u], to[tot] = v, last[u] = tot;}
void findC(int u)
{
siz[u] = 1, son[u] = 0;
bool ok = 1;
for(int i = last[u]; i; i = pre[i])
{
int v = to[i];
if(v == fa[u]) continue ;
fa[v] = u, findC(v), ++son[u], siz[u] += siz[v];
if(siz[v] > (n >> 1)) ok = 0;
}
if(ok && n - siz[u] <= (n >> 1)) c[++c[0]] = u;
}
void delEdge(int u, int v)
{
if(to[last[u]] ^ v)
for(int i = last[u]; pre[i]; i = pre[i])
if(to[pre[i]] == v && (pre[i] = pre[pre[i]])) return ;
last[u] = pre[last[u]];
}
void dfs(int x, int y)
{
f[x][y] = INF;
vector<int> sx, sy;
int full = p2[son[y]] - 1, s = son[x];
if(s != son[y] || siz[x] != siz[y]) return ;
for(int i = last[x]; i; i = pre[i])
if(to[i] ^ fa[x]) sx.push_back(to[i]);
for(int i = last[y]; i; i = pre[i])
if(to[i] ^ fa[y]) sy.push_back(to[i]);
fo(i, 1, s) fo(j, 1, s)
dfs(sx[i - 1], sy[j - 1]);
fo(i, 1, s) memset(g[i], 0x3f, sizeof(int) * (full + 3));
fo(i, 1, s) fo(j, 1, s) fo(S, p2[j - 1], full)
if(cnt[S] == i && (S & p2[j - 1]))
g[i][S] = min(g[i][S], g[i - 1][S ^ p2[j - 1]] + f[sx[i - 1]][sy[j - 1]]);
f[x][y] = min(f[x][y], g[s][full] + (op1[x] != op2[y]));
}
int main()
{
n = read();
fo(i, 1, 10) p2[i] = p2[i - 1] << 1;
fo(i, 1, p2[10]) cnt[i] = cnt[i >> 1] + (i & 1);
int u, v, rt;
fo(i, 2, n) u = read(), v = read(), add(u, v), add(v, u);
fo(i, 1, n) op1[i] = read();
fo(i, 1, n) op2[i] = read();
findC(1);
if(c[0] ^ 1)
{
rt = ++n; add(n, c[1]), add(n, c[2]), add(c[1], n), add(c[2], n);
delEdge(c[1], c[2]), delEdge(c[2], c[1]);
}
else rt = c[1];
fa[rt] = 0, findC(rt);
dfs(rt, rt);
printf("%d\n", f[rt][rt]);
return 0;
}

JZOJ 3296 Luogu P3296 [SDOI2013]刺客信条的更多相关文章

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

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

  2. luogu P3305 [SDOI2013]费用流

    题目链接 bz似乎挂了... luogu P3305 [SDOI2013]费用流 题解 dalao告诉我,这题 似乎很水.... 懂了题目大意就可以随便切了 问1,最大流 问2,二分最大边权求,che ...

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

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

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

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

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

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

  6. BZOJ 3203 Luogu P3299 [SDOI2013]保护出题人 (凸包、斜率优化、二分)

    惊了,我怎么这么菜啊.. 题目链接: (bzoj)https://www.lydsy.com/JudgeOnline/problem.php?id=3203 (luogu)https://www.lu ...

  7. Luogu P3305 [SDOI2013]费用流 二分 网络流

    题目链接 \(Click\) \(Here\) 非常有趣的一个题目. 关键结论:所有的单位费用应该被分配在流量最大的边上. 即:在保证最大流的前提下,使最大流量最小.这里我们采用二分的方法,每次判断让 ...

  8. luogu P3297 [SDOI2013]逃考

    传送门 gugugu 首先每个人管理的区域是一个多边形,并且整个矩形是被这样的多边形填满的.现在的问题是求一条经过多边形最少的路径到达边界,这个可以最短路. 现在的问题是建图,显然我们应该给相邻的多边 ...

  9. luogu P3304 [SDOI2013]直径

    树的直径两遍dfs救星了 至于一定在直径中的边数,可以发现这些边一定是连续的(不然你两条直径中间能有空挡?),然后,如果某个点往下有多条直径,那么这条点以下都不算入答案.所以以直径分别两端点为根,找出 ...

随机推荐

  1. ebs 初始化登陆

    BEGIN fnd_global.APPS_INITIALIZE(user_id => youruesr_id, esp_id => yourresp_id, resp_appl_id = ...

  2. 06 - Vue3 UI Framework - Dialog 组件

    做完按钮之后,我们应该了解了遮罩层的概念,接下来我们来做 Dialog 组件! 返回阅读列表点击 这里 需求分析 默认是不可见的,在用户触发某个动作后变为可见 自带白板卡片,分为上中下三个区域,分别放 ...

  3. Linux下安装中文字体

    目录 一.Centos系列 二.Ubuntu系列 一.Centos系列 1.安装字体库 yum -y install fontconfig 2.添加中文字体,建立存储中文字体的文件夹 mkdir /u ...

  4. MySQL如何使用WITH ROLLUP函数

    一.WITH ROLLUP函数适用于跟在GROUP BY 字段后面进行分组求和使用 SELECT t1.`产品名称`,SUM(t1.`数量`),SUM(t1.`金额`),t1.`日期` FROM sh ...

  5. canvas绘制圣诞树

    最近不知道咋的都玩起了用备忘录手绘圣诞树,作为万能的程序员,肯定也要跟上潮流,那用代码来实现圣诞树吧.哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 实现效果如下: ...

  6. 关于某 App 请求参数 sign 字段加密分析

    受害者: 6ZqG5LyX5pWw5o2u 通过 Charles 抓包发现关键信息请求均携带 sign 参数,且每次请求的值都不一样: 使用 jadx 将对应的 apk 反编译并分析,全局搜素 &qu ...

  7. python selenium 多账户自动登入163邮箱

    pycharm一些快捷键: ' ctrl ' +' / ' :注释 ' Tab ' :同时缩进 ' shift ' +' Tab ' :左移 一次缩进 本文webinfo.txt路径:C:\Pytho ...

  8. CF1492B Card Deck 题解

    Content 有 \(n\) 张纸牌组成的一个牌堆,每张纸牌都有一个价值 \(p_1,p_2,\dots,p_n\).每次选出最顶上的几个牌放到另外一个一开始为空的牌堆里面.定义一个牌堆的总值为 \ ...

  9. CPU中断数查看与网卡中断绑核

    CPU中断数查看 多核CPU每个核心CPU发生中断的数量查看 # mpstat -I SUM -P ALL 1 3 Linux 5.4.0-40-generic (verify-new-511kern ...

  10. Android 运行C可执行程序

    p.p1 { margin: 0; font: 12px "Helvetica Neue"; color: rgba(69, 69, 69, 1) } p.p2 { margin: ...