经过长时间的思考,我发现直接考虑对一条链进行修改是很难做出本题的,可能需要换一个方向。

可以发现本题中有操作的存在,是没有可以反过来做的做法的,因此正难则反这条路应该走不通。

那么唯一的办法就是简化这个操作或是转化问题了,不难发现前者应该是后者的基础,于是我们应该先将重心放在前者上。

可以发现本题是对一条链进行同一个操作,那么能否使用差分来使得多点操作变成单点操作呢?

不难发现由于差分的优秀性质是可以的,对于一次修改 \((x, y, k)\),我们只需要要在 \(x, y\) 处分别异或 \(k\),那么最终每条边的真实值就是这条边底下的点所在子树内修改的异或之和。

因为对于不在 \(x, y\) 链上的点,计算异或和时 \(x, y\) 会算两次相抵消。

但你很快就会发现一个问题,最终所有边全为 \(0\) 的条件是什么?

你会认为是所有点权为 \(0\),但事实上并非如此。

因为上述的差分事实上是将边权下方到点权的过程,那么根的权值应该是什么呢?

你会发现因为根上面没有边,所以这是一个未定义的问题,是不可以解决的。

那么只能再换一种思路了,不难发现我们我们的目的在于减少修改的点权,那么上面的过程暗示着我们要尽可能异或两次将中间的操作抵消。

不难发现的是,因为在一次链修改 \((x, y, k)\) 当中,除了 \(x, y\) 以外其他链上的点都连着恰好两条在链上的边。

那么我们只要定义每个点的点权包含这两点的异或和即可。

不难发现这个定义的点权要具有普适性,因此我们需要将每个点的点权定义为周边所有边的异或和。

那么这样每次操作只需修改两个点的点权即可。

你会发现上面的未定义问题也消失了,最终的状态不难发现就是所有点的点权为 \(0\)(从叶子开始向上归纳证明即可)。

那么现在的问题就转化成给定 \(n\) 个点权 \(a(a_i \le 15)\),每次操作选择两个点权异或上 \(x\) 求最小的操作次数使得所有点权变为 \(0\)。

那么你会发现每次选择两个相同的数直接异或这两个数是一定最优的,但这样也会产生一个问题:会不会经过这样一次操作后使得操作无解?

那么我们一定要思考的一个东西就是答案有解的充要条件。

直接寻找充分条件是不好找的,我们一般先寻找一些必要条件再组合成充要条件。

不难发现因为每次又是同时异或两个点,那么同样的总异或和还是不变的,那么一开始的总异或和为 \(0\) 是否是有解的充要条件呢?

不难发现是的,只需要每次选定两个点然后将其中一个消去即可。因为最终异或和为 \(0\),就一定不会剩下一个数落单的情况。

于是就可以发现不论我们进行什么操作,都是不会影响解的存在性的,那么就可放心地去寻找最优策略了。

回到最开始的想法,每次找到两个相同的权值直接同时消去显然是最优的。

那么这样一来最终我们剩下的点权就只有 \(16\) 个了。

还需要注意的是,\(0\) 是不需要消的。因为如果 \(0\) 和 \(a\) 消了一个 \(x\),再和 \(b\) 消回来,是等价于 \(a, b\) 消这个数的。

不难发现当点权没有重复时是不能直接贪心的,因为选择不一样的两个点会影响的状态也有所不同,那么只能考虑使用 \(dp\)。

因为点权非常少,可以直接考虑使用状压 \(dp\)。

很简单的,我们可以直接令 \(dp_S\) 为当前剩余数状态为 \(S\) 所需的最小操作次数。

那么每次转移需要枚举 \(S\) 中的两个点 \(i, j\),对 \(i, j\) 进行一次操作。

一种很直接的想法就是将 \(i, j\) 中的一个消去留下另一个,那么这样是否会更优呢?

不难发现确实是的,因为如果假设 \(a, b\) 异或上了 \(x\),最优情况下就是 \(S\) 中同时存在 \(c, d\) 使得 \(\rm a \ xor \ x = c, b \ xor \ x = d\)。

此时消去四个数也需要 \(3\) 次操作,不难发现每次至少消一个数也最多只要消 \(3\) 次。

于是每次转移到的状态就是 \(\rm S \ xor \ 2 ^ i \ xor \ 2 ^ j \ xor \ 2 ^ {i \ xor \ j}\),那么如果 \(S\) 中存在 \(\rm i \ xor \ j\),根据最开始的理论可知直接消去即可。

可以注意到的是每次转移到的状态总是比 \(S\) 小的,因此这个 \(dp\) 是有效的,从大往小转移即可。

#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define dep(i, l, r) for (int i = r; i >= l; --i)
const int N = 100000 + 5;
const int M = (1 << 16) + 5;
int n, u, v, w, ans, fir, a[N], dp[N], cnt[N];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int main() {
n = read(), memset(dp, 0x7f, sizeof(dp));
rep(i, 1, n - 1) u = read() + 1, v = read() + 1, w = read(), a[u] ^= w, a[v] ^= w;
rep(i, 1, n) ++cnt[a[i]];
rep(i, 1, 15) ans += cnt[i] / 2, cnt[i] %= 2;
rep(i, 1, 15) if(cnt[i]) fir += (1 << i);
dp[fir] = 0;
dep(S, 1, fir) if(dp[S] < n){
rep(i, 0, 15) if((1 << i) & S) {
rep(j, 0, 15) if((1 << j) & S) {
int val = 1, sta = S ^ (1 << i) ^ (1 << j) ^ (1 << (i ^ j));
if(S & (1 << (i ^ j))) ++val;
dp[sta] = min(dp[sta], dp[S] + val);
}
}
}
printf("%d", ans + dp[0]);
return 0;
}

AT3913 XOR Tree的更多相关文章

  1. AT3913 XOR Tree(巧妙转换+状压dp)

    Step1:首先定义一个点的权值为与其相连边的异或和.那么修改一条路径,权值改变的只有两个端点.边权都为0和点权都为0实质相同. Step2:那么现在和树的结构就没有什么关系了.每次选两个点,然后同时 ...

  2. [多校联考2019(Round 5 T1)] [ATCoder3912]Xor Tree(状压dp)

    [多校联考2019(Round 5)] [ATCoder3912]Xor Tree(状压dp) 题面 给出一棵n个点的树,每条边有边权v,每次操作选中两个点,将这两个点之间的路径上的边权全部异或某个值 ...

  3. 「AGC035C」 Skolem XOR Tree

    「AGC035C」 Skolem XOR Tree 感觉有那么一点点上道了? 首先对于一个 \(n\),若 \(n\equiv 3 \pmod 4\),我们很快能够构造出一个合法解如 \(n,n-1, ...

  4. 【思维题 状压dp】APC001F - XOR Tree

    可能算是道中规中矩的套路题吧…… Time limit : 2sec / Memory limit : 256MB Problem Statement You are given a tree wit ...

  5. AtCoder - 3913 XOR Tree

    Problem Statement You are given a tree with N vertices. The vertices are numbered 0 through N−1, and ...

  6. Solution -「AT 3913」XOR Tree

    \(\mathcal{Description}\)   Link.   给定一棵树,边 \((u,v)\) 有边权 \(w(u,v)\).每次操作可以使一条简单路径上的边权异或任意非负整数.求最少的操 ...

  7. P1230: [Usaco2008 Nov]lites 开关灯

    嗯嗯,这是一道线段树的题,询问区间内亮着的灯的个数,我们可以把区间修改的线段树改一下,原本的求和改成若有奇数次更改则取反(总长度-亮着的灯个数),而判断是否奇数次只要数组加一个delta的值,upda ...

  8. Petrozavodsk Summer-2016. Ural FU Dandelion Contest

    A. Square Function 留坑. B. Guess by Remainder 询问$lcm(1,2,3,...,n)-1$即可一步猜出数. 计算$lcm$采用分治FFT即可,时间复杂度$O ...

  9. 【AtCoder】AtCoder Petrozavodsk Contest 001

    A - Two Integers 如果\(X\)是\(Y\)的倍数的话不存在 可以输出\(X \cdot (\frac{Y}{gcd(X,Y)} - 1)\) 代码 #include <bits ...

随机推荐

  1. matplotlib 进阶之Tight Layout guide

    目录 简单的例子 Use with GridSpec Legend and Annotations Use with AxesGrid1 Colorbar 函数链接 matplotlib教程学习笔记 ...

  2. null和空字符串对于查询where条件语句的影响

    在数据库中我们进行数据处理的过程中,对于null值或者空字符串的情况对于这种数据我们进行计算平均值以及查询过程中如何进行对于这类数据的处理呢? step1:建表:create table a(id i ...

  3. Spring企业级程序设计 • 【第5章 Spring MVC快速入门】

    全部章节   >>>> 本章目录 5.1 Spring MVC设计概述及其框架结构 5.1.1 Spring MVC介绍 5.1.1 Spring MVC优势 5.1.2  S ...

  4. 移动端的样式重置(CSS RESET)

    /********** * reset *********/ * {box-sizing: border-box; -webkit-tap-highlight-color: rgba(0,0,0,0) ...

  5. 来自MyBatis不一样收获结果的探索之旅-v3.5.9

    概述 定义 MyBatis官网 https://mybatis.org/mybatis-3/ 最新版本为3.5.9 MyBatis是一个的ORM框架,支持自定义SQL.存储过程和高级映射.MyBati ...

  6. Linux 使用 split 命令分割文件

    使用方法: $ split --help 用法:split [选项]... [输入 [前缀]] 将输入内容拆分为固定大小的片段并输出到"前缀aa"."前缀ab" ...

  7. mysql语句2-单表查询

    mysql 查询以及多表查询 以下所有表格样例都采用下边这个表格 mysql> select * from benet; +------+------+----------+ | id   | ...

  8. Flask_安装和配置(一)

    安装Flask pip install flask 一 .创建Flask项目 Flask与Django相比,没有提供任何自动创建项目的操作,所以需要手动创建项目及启动项目的管理文件 例如,创建项目目录 ...

  9. 深入研究 synchronized 同步锁 作用于 静态方法 和 非静态方法 的 区别

    1.前言 众所周知, synchronized 是同步锁 ,虽然在底层又细分了无锁.偏向锁.轻量级锁.自旋锁 以及重量级锁 机制, 这些底层锁知道一下原理即可 ,[想要 了解 这篇 博文 有 解释 : ...

  10. SSM简单实现文件上传和下载

    一.配置spring-mvc <!-- 配置多媒体文件解析器 --> <bean id="multipartResolver" class="org.s ...