【BZOJ3197】[SDOI2013]刺客信条

题面

bzoj

洛谷

题解

关于树的同构,有一个非常好的性质:

把树的重心抠出来,那么会出现两种情况:

1.有一个重心,那么我们直接把这个重心作为树的根。

2.有多个重心,这些重心一定有一条边相连,设重心为\(u,v\),那么把\(u,v\)断开,用一个新的点把

\(u,v\)连起来,将这个点作为根。

最终同构当且仅当与左右两子树分别同构。

有了这条性质,我们继续往下考虑:

设\(f[x][y]\)表示\(x\)的子树与\(y\)的子树同构的最小代价,

怎么转移?

可以分别用\(x,y\)儿子匹配中间的代价来做,

因为他们的儿子肯定是个完美匹配

直接用\(KM\)或费用流进行二分图最大权匹配即可。

对于判断两子树是否相同树哈希即可。

代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cstring>
  5. #include <cmath>
  6. #include <algorithm>
  7. #include <queue>
  8. #include <vector>
  9. using namespace std;
  10. inline int gi() {
  11. register int data = 0, w = 1;
  12. register char ch = 0;
  13. while (!isdigit(ch) && ch != '-') ch = getchar();
  14. if (ch == '-') w = -1, ch = getchar();
  15. while (isdigit(ch)) data = 10 * data + ch - '0', ch = getchar();
  16. return w * data;
  17. }
  18. typedef unsigned long long ull;
  19. const int MAX_N = 1405;
  20. namespace Sol {
  21. const int INF = 1e9;
  22. struct Graph {
  23. int to, next, cap, cost, rev;
  24. } e[MAX_N << 4];
  25. int fir[MAX_N], e_cnt, S, T;
  26. void clearGraph() { fill(&fir[S], &fir[T + 1], -1); e_cnt = 0; }
  27. void Add_Edge(int u, int v, int cap, int cost) {
  28. e[e_cnt] = (Graph){v, fir[u], cap, cost, e_cnt + 1};
  29. fir[u] = e_cnt++;
  30. e[e_cnt] = (Graph){u, fir[v], 0, -cost, e_cnt - 1};
  31. fir[v] = e_cnt++;
  32. }
  33. int dis[MAX_N], preve[MAX_N], prevv[MAX_N];
  34. bool inq[MAX_N];
  35. int min_cost_flow(int s, int t) {
  36. static queue<int> que; int cost = 0;
  37. while (1) {
  38. fill(&dis[s], &dis[t + 1], INF);
  39. fill(&inq[s], &inq[t + 1], 0);
  40. inq[s] = 1, dis[s] = 0, que.push(s);
  41. while (!que.empty()) {
  42. int x = que.front(); que.pop();
  43. for (int i = fir[x]; ~i; i = e[i].next) {
  44. int v = e[i].to;
  45. if (dis[x] + e[i].cost < dis[v] && e[i].cap > 0) {
  46. dis[v] = dis[x] + e[i].cost;
  47. preve[v] = i, prevv[v] = x;
  48. if (!inq[v]) que.push(v), inq[v] = 1;
  49. }
  50. }
  51. inq[x] = 0;
  52. }
  53. if (dis[t] == INF) return cost;
  54. int d = INF;
  55. for (int x = t; x != s; x = prevv[x]) d = min(e[preve[x]].cap, d);
  56. cost += dis[t] * d;
  57. for (int x = t; x != s; x = prevv[x]) {
  58. e[preve[x]].cap -= d;
  59. e[e[preve[x]].rev].cap += d;
  60. }
  61. }
  62. }
  63. }
  64. struct Graph { int to, next; } e[MAX_N << 1]; int fir[MAX_N], e_cnt = 0;
  65. void clearGraph() { memset(fir, -1, sizeof(fir)); e_cnt = 0; }
  66. void Add_Edge(int u, int v) { e[e_cnt] = (Graph){v, fir[u]}; fir[u] = e_cnt++; }
  67. int N, a[MAX_N], b[MAX_N], F[MAX_N], size[MAX_N], Rt;
  68. void getRoot(int x, int fa) {
  69. size[x] = 1, F[x] = 0;
  70. for (int i = fir[x]; ~i; i = e[i].next) {
  71. int v = e[i].to; if (v == fa) continue;
  72. getRoot(v, x); size[x] += size[v];
  73. F[x] = max(F[x], size[v]);
  74. }
  75. F[x] = max(F[x], N - size[x]);
  76. if (F[x] < F[Rt]) Rt = x;
  77. }
  78. vector<int> vec1[MAX_N], vec2[MAX_N];
  79. ull hs[MAX_N]; int f[MAX_N][MAX_N], c[MAX_N][MAX_N];
  80. bool cmp(const int &i, const int &j) { return hs[i] < hs[j]; }
  81. void dfs(int x, int fa, vector<int> *vec) {
  82. size[x] = 1, hs[x] = 0, vec[x].clear();
  83. for (int i = fir[x]; ~i; i = e[i].next) {
  84. int v = e[i].to; if (v == fa) continue;
  85. dfs(v, x, vec); size[x] += size[v], vec[x].push_back(v);
  86. }
  87. sort(vec[x].begin(), vec[x].end(), cmp);
  88. for (int i = vec[x].size() - 1; ~i; --i) hs[x] = hs[x] * N + hs[vec[x][i]];
  89. hs[x] = hs[x] * N + size[x];
  90. }
  91. int solve(int n) {
  92. Sol::S = 0, Sol::T = n << 1 | 1;
  93. Sol::clearGraph();
  94. for (int i = 1; i <= n; i++) {
  95. Sol::Add_Edge(Sol::S, i, 1, 0), Sol::Add_Edge(i + n, Sol::T, 1, 0);
  96. for (int j = 1; j <= n; j++) Sol::Add_Edge(i, j + n, 1, c[i][j]);
  97. }
  98. return Sol::min_cost_flow(Sol::S, Sol::T);
  99. }
  100. int Dp(int x, int y) {
  101. if (f[x][y] != -1) return f[x][y];
  102. f[x][y] = a[x] ^ b[y];
  103. for (int i = 0, j, sz = vec1[x].size() - 1; i <= sz; i++) {
  104. j = i; while (j < sz && hs[vec1[x][j + 1]] == hs[vec1[x][i]]) ++j;
  105. for (int k = i; k <= j; k++)
  106. for (int l = i; l <= j; l++)
  107. Dp(vec1[x][k], vec2[y][l]);
  108. for (int k = i; k <= j; k++)
  109. for (int l = i; l <= j; l++)
  110. c[k - i + 1][l - i + 1] = Dp(vec1[x][k], vec2[y][l]);
  111. f[x][y] += solve(j - i + 1); i = j;
  112. }
  113. return f[x][y];
  114. }
  115. int main () {
  116. clearGraph();
  117. N = gi(), F[0] = N;
  118. for (int i = 1; i < N; i++) {
  119. int u = gi(), v = gi();
  120. Add_Edge(u, v), Add_Edge(v, u);
  121. }
  122. for (int i = 1; i <= N; i++) a[i] = gi();
  123. for (int i = 1; i <= N; i++) b[i] = gi();
  124. getRoot(1, 0); dfs(Rt, 0, vec2); ull res = hs[Rt];
  125. int ans = N;
  126. for (int i = 1; i <= N; i++) {
  127. dfs(i, 0, vec1);
  128. if (hs[i] == res) {
  129. memset(f, -1, sizeof(f));
  130. ans = min(ans, Dp(i, Rt));
  131. }
  132. }
  133. printf("%d\n", ans);
  134. return 0;
  135. }

一些题外话

\(KM\)与费用流的速度对比(不开O2):

我的费用流:

\(xgzc\)的辣鸡\(KM\):

\(zzx\)的豪华版\(KM\):

所以用费用流也不是不行

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

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

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

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

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

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

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

  4. Bzoj3197: [Sdoi2013]assassin

    题面 传送门 Sol 套路:找出重心,如果有两个就新建一个点 然后把这棵树hash一下 设\(f[i][j]\)表示第一颗树到\(i\)第二棵树到\(j\),子树\(i,j\)同构的付出的最小代价 转 ...

  5. [SDOI2013]刺客信条

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

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

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

  7. 【BZOJ3197】[Sdoi2013]assassin 树同构+动态规划+KM

    [BZOJ3197][Sdoi2013]assassin Description Input Output Sample Input 4 1 2 2 3 3 4 0 0 1 1 1 0 0 0 Sam ...

  8. [JZOJ3296] 【SDOI2013】刺客信条

    题目 题目大意 给你一棵树,树上每个节点有000或111的状态. 用最少的操作次数使得当前状态与目标状态同构. 思考历程 首先想到的是找重心. 因为根是不确定的,但重心只会有一个或两个,以重心为根就能 ...

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

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

随机推荐

  1. POJ-2452 Sticks Problem 二分+RMQ

    题目链接: https://cn.vjudge.net/problem/POJ-2452 题目大意: 给出一个数组a,求最大的j-i满足 i<j && a[i] ... a[j] ...

  2. 6、Spring Cloud -熔断器Hystrix

    6.1.什么是Hystrix 在分布式系统中.服务与服务之间的依赖错综复杂,一种不可避免的情况就是某些服务 出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞.   Hystrix是Netfli ...

  3. laravel 多态映射(打赏为例)

    迁移: public function up() { Schema::create('rewards', function (Blueprint $table) { $table->increm ...

  4. SpringBoot实战(十三)之缓存

    什么是缓存? 引用下百度百科的解释: 缓存就是数据交换的缓冲区(又称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,找到了则直接执行,找不到的话则从内存中查找.由于缓存的运行速度 ...

  5. Nginx之动静分离

    为什么要动静分离呢? 拿Nginx来说,Nginx是Web服务器,仅仅只能处理静态资源(例如js,img,css等等),而Tomcat属于应用服务器既能处理静态资源又能处理动态资源(例如jsp,fre ...

  6. PL/SQL Developer编码格式设置

    通过PL/SQL中文字段显示乱码或者导出数据出现乱码,原因是数据库的编码格式和PL/SQL的编码格式不统一导致. 查看ORACLE数据库字符集: select userenv('language') ...

  7. Eclipse常用快捷键(持续更新)

    编辑相关快捷键 Eclipse的编辑功能非常强大,掌握了Eclipse快捷键功能,能够大大提高开发效率.Eclipse中有如下一些和编辑相关的快捷键. 1.[Ctrl+O] 显示类中方法和属性的大纲, ...

  8. "strace -p"非常有用,它减少了很多猜测工作,也不需要重新启动应用。lsof -p process_id +iostat + sar -n DEV 1

    linux神器strace - youxin - 博客园https://www.cnblogs.com/youxin/p/8837771.html 某个进程突然占用了很多CPU? 或者某个进程看起来像 ...

  9. 解决Js跨域访问的问题

    1,最近有个需求,用Js获取Html标签<input type="file"/>的路径!遇到代码拒绝访问,提示安全验证,不允许跨域访问,简单的设置一下浏览器即可,不过对 ...

  10. vue+echarts实现可拖动节点的折现图(支持拖动方向和上下限的设置)

    本篇文档主要是利用echarts实现可拖动节点的折现图,在echarts中找到了一个demo,传送门:https://echarts.baidu.com/examples/editor.html?c= ...