【BZOJ3197】[SDOI2013]刺客信条
【BZOJ3197】[SDOI2013]刺客信条
题面
题解
关于树的同构,有一个非常好的性质:
把树的重心抠出来,那么会出现两种情况:
1.有一个重心,那么我们直接把这个重心作为树的根。
2.有多个重心,这些重心一定有一条边相连,设重心为\(u,v\),那么把\(u,v\)断开,用一个新的点把
\(u,v\)连起来,将这个点作为根。
最终同构当且仅当与左右两子树分别同构。
有了这条性质,我们继续往下考虑:
设\(f[x][y]\)表示\(x\)的子树与\(y\)的子树同构的最小代价,
怎么转移?
可以分别用\(x,y\)儿子匹配中间的代价来做,
因为他们的儿子肯定是个完美匹配
直接用\(KM\)或费用流进行二分图最大权匹配即可。
对于判断两子树是否相同树哈希即可。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
inline int gi() {
register int data = 0, w = 1;
register char ch = 0;
while (!isdigit(ch) && ch != '-') ch = getchar();
if (ch == '-') w = -1, ch = getchar();
while (isdigit(ch)) data = 10 * data + ch - '0', ch = getchar();
return w * data;
}
typedef unsigned long long ull;
const int MAX_N = 1405;
namespace Sol {
const int INF = 1e9;
struct Graph {
int to, next, cap, cost, rev;
} e[MAX_N << 4];
int fir[MAX_N], e_cnt, S, T;
void clearGraph() { fill(&fir[S], &fir[T + 1], -1); e_cnt = 0; }
void Add_Edge(int u, int v, int cap, int cost) {
e[e_cnt] = (Graph){v, fir[u], cap, cost, e_cnt + 1};
fir[u] = e_cnt++;
e[e_cnt] = (Graph){u, fir[v], 0, -cost, e_cnt - 1};
fir[v] = e_cnt++;
}
int dis[MAX_N], preve[MAX_N], prevv[MAX_N];
bool inq[MAX_N];
int min_cost_flow(int s, int t) {
static queue<int> que; int cost = 0;
while (1) {
fill(&dis[s], &dis[t + 1], INF);
fill(&inq[s], &inq[t + 1], 0);
inq[s] = 1, dis[s] = 0, que.push(s);
while (!que.empty()) {
int x = que.front(); que.pop();
for (int i = fir[x]; ~i; i = e[i].next) {
int v = e[i].to;
if (dis[x] + e[i].cost < dis[v] && e[i].cap > 0) {
dis[v] = dis[x] + e[i].cost;
preve[v] = i, prevv[v] = x;
if (!inq[v]) que.push(v), inq[v] = 1;
}
}
inq[x] = 0;
}
if (dis[t] == INF) return cost;
int d = INF;
for (int x = t; x != s; x = prevv[x]) d = min(e[preve[x]].cap, d);
cost += dis[t] * d;
for (int x = t; x != s; x = prevv[x]) {
e[preve[x]].cap -= d;
e[e[preve[x]].rev].cap += d;
}
}
}
}
struct Graph { int to, next; } e[MAX_N << 1]; int fir[MAX_N], e_cnt = 0;
void clearGraph() { memset(fir, -1, sizeof(fir)); e_cnt = 0; }
void Add_Edge(int u, int v) { e[e_cnt] = (Graph){v, fir[u]}; fir[u] = e_cnt++; }
int N, a[MAX_N], b[MAX_N], F[MAX_N], size[MAX_N], Rt;
void getRoot(int x, int fa) {
size[x] = 1, F[x] = 0;
for (int i = fir[x]; ~i; i = e[i].next) {
int v = e[i].to; if (v == fa) continue;
getRoot(v, x); size[x] += size[v];
F[x] = max(F[x], size[v]);
}
F[x] = max(F[x], N - size[x]);
if (F[x] < F[Rt]) Rt = x;
}
vector<int> vec1[MAX_N], vec2[MAX_N];
ull hs[MAX_N]; int f[MAX_N][MAX_N], c[MAX_N][MAX_N];
bool cmp(const int &i, const int &j) { return hs[i] < hs[j]; }
void dfs(int x, int fa, vector<int> *vec) {
size[x] = 1, hs[x] = 0, vec[x].clear();
for (int i = fir[x]; ~i; i = e[i].next) {
int v = e[i].to; if (v == fa) continue;
dfs(v, x, vec); size[x] += size[v], vec[x].push_back(v);
}
sort(vec[x].begin(), vec[x].end(), cmp);
for (int i = vec[x].size() - 1; ~i; --i) hs[x] = hs[x] * N + hs[vec[x][i]];
hs[x] = hs[x] * N + size[x];
}
int solve(int n) {
Sol::S = 0, Sol::T = n << 1 | 1;
Sol::clearGraph();
for (int i = 1; i <= n; i++) {
Sol::Add_Edge(Sol::S, i, 1, 0), Sol::Add_Edge(i + n, Sol::T, 1, 0);
for (int j = 1; j <= n; j++) Sol::Add_Edge(i, j + n, 1, c[i][j]);
}
return Sol::min_cost_flow(Sol::S, Sol::T);
}
int Dp(int x, int y) {
if (f[x][y] != -1) return f[x][y];
f[x][y] = a[x] ^ b[y];
for (int i = 0, j, sz = vec1[x].size() - 1; i <= sz; i++) {
j = i; while (j < sz && hs[vec1[x][j + 1]] == hs[vec1[x][i]]) ++j;
for (int k = i; k <= j; k++)
for (int l = i; l <= j; l++)
Dp(vec1[x][k], vec2[y][l]);
for (int k = i; k <= j; k++)
for (int l = i; l <= j; l++)
c[k - i + 1][l - i + 1] = Dp(vec1[x][k], vec2[y][l]);
f[x][y] += solve(j - i + 1); i = j;
}
return f[x][y];
}
int main () {
clearGraph();
N = gi(), F[0] = N;
for (int i = 1; i < N; i++) {
int u = gi(), v = gi();
Add_Edge(u, v), Add_Edge(v, u);
}
for (int i = 1; i <= N; i++) a[i] = gi();
for (int i = 1; i <= N; i++) b[i] = gi();
getRoot(1, 0); dfs(Rt, 0, vec2); ull res = hs[Rt];
int ans = N;
for (int i = 1; i <= N; i++) {
dfs(i, 0, vec1);
if (hs[i] == res) {
memset(f, -1, sizeof(f));
ans = min(ans, Dp(i, Rt));
}
}
printf("%d\n", ans);
return 0;
}
一些题外话
\(KM\)与费用流的速度对比(不开O2):
我的费用流:

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

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

所以用费用流也不是不行
【BZOJ3197】[SDOI2013]刺客信条的更多相关文章
- [BZOJ3197][SDOI2013]刺客信条assassin
bzoj luogu Description 故事发生在1486 年的意大利,Ezio原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一名刺客.最终,凭借着他的努力和出众的 ...
- BZOJ3197:[SDOI2013]刺客信条——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=3197 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受 ...
- Bzoj3197/洛谷3296 [SDOI2013]刺客信条assassin(树的重心+树Hash+树形DP+KM)
题面 Bzoj 洛谷 题解 (除了代码均摘自喻队的博客,可是他退役了) 首先固定一棵树,枚举另一棵树,显然另一棵树只有与这棵树同构才有可能产生贡献 如果固定的树以重心为根,那么另一棵树最多就只有重心为 ...
- Bzoj3197: [Sdoi2013]assassin
题面 传送门 Sol 套路:找出重心,如果有两个就新建一个点 然后把这棵树hash一下 设\(f[i][j]\)表示第一颗树到\(i\)第二棵树到\(j\),子树\(i,j\)同构的付出的最小代价 转 ...
- [SDOI2013]刺客信条
Description 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一名刺客.最终,凭借着他的努力和出众的天赋,成为了杰出的刺 ...
- JZOJ 3296 Luogu P3296 [SDOI2013]刺客信条
前言 做法来自:@pzrpzr ,写一下!Orz pzr! 题目大意 \(n\) 个点的无根树,每个点有两个 \(0/1\) 权值,合适地安排节点在同构树中的顺序,使得前后对应的权值不同节点个数最小, ...
- 【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 ...
- [JZOJ3296] 【SDOI2013】刺客信条
题目 题目大意 给你一棵树,树上每个节点有000或111的状态. 用最少的操作次数使得当前状态与目标状态同构. 思考历程 首先想到的是找重心. 因为根是不确定的,但重心只会有一个或两个,以重心为根就能 ...
- 【JZOJ3296】【SDOI2013】刺客信条(assassin)
╰( ̄▽ ̄)╭ Description 故事发生在1486 年的意大利,Ezio 原本只是一个文艺复兴时期的贵族,后来因为家族成员受到圣殿骑士的杀害,决心成为一名刺客.最终,凭借着他的努力和出众的天赋 ...
随机推荐
- 死磕salt系列-salt grains pillar 配置
grains 和 pillar 对比: Grains:存放静态数据,主要存储客户端的主机信息,重启grains会刷新. Pillar: 处理敏感数据, 处理差异性的文件. Grains数据系统 sal ...
- luogu P3381【模板】最小费用最大流
嘟嘟嘟 没错,我开始学费用流了! 做法也是比较朴素的\(spfa\). 就是每一次以费用为权值跑一遍\(spfa\)找到一条最短路,然后把这条道全流满,并把这一次的流量和费用累加到答案上.因此我们需要 ...
- 为什么要使用GetSafeHwnd()函数
当我们想得到一个窗口对象(CWnd的派生对象)指针的句柄(HWND)时,最安全的方法是使用GetSafeHwnd()函数,通过下面的例子来看其理由: CWnd *pwnd = FindWindow(“ ...
- (转)python类class中_init_函数以及参数self的简单解释
1)_init_函数(方法) #-*- encoding:utf-8 -*- class NewClass(object): def __init__(self,name): print self s ...
- WebSocket的原理,以及和Http的关系 (转载)
一.WebSocket是HTML5中的协议,支持持久连接:而Http协议不支持持久连接. 首先HTMl5指的是一系列新的API,或者说新规范,新技术.WebSocket是HTML5中新协议.新API. ...
- SharePoint客户端对象模型—任务日历生成
1,憋了好几天在经理帮助下用Js根据任务列表,生成的个人任务日历. (1)需要用到的CSS样式 <style type="text/css"> th.ms-vh { c ...
- 如何快速找到指定端口被哪个程序占用并释放该端口(解决bindException)
首先打开打开任务管理器,选择性能模块,下方有打开资源监视器,或者直接搜索资源监视器 在资源监视器中点击侦听端口模块,即可看到正在使用网络端口的应用程序名和pid,如果被占用可以直接使用命令行关闭即可 ...
- HashMap源码阅读与解析
目录结构 导入语 HashMap构造方法 put()方法解析 addEntry()方法解析 get()方法解析 remove()解析 HashMap如何进行遍历 一.导入语 HashMap是我们最常见 ...
- JAVA 8 新特性 __ Optional 类
Optional 类是一个可以作为null容器的对象,若值存在调用isPresent()就返回 true,调用get()会返回该对象. Optional是一个容器,可以保存类型T的值,或者仅仅保存nu ...
- mina 通讯框架
Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP.UDP/IP协议栈的通信框架(当然,也可以提供JAVA 对象的序列化服务.虚拟机管道通信服务等),M ...