[Atcoder] XOR Tree
题目
点这里看题目。
分析
奇奇妙妙的题目。
直接修改树上的路径会影响到很多条边,并不方便处理。我们需要压缩受影响信息的数量。
由于对点的处理更加灵活,因而我们考虑将边权转为点权。
考虑修改树上路径经常与树上差分挂钩,我们可以猜想第一种方法:给点赋权为它到根上所有边的权的异或和。
但是没有什么用,因为一次操作仍然会影响到很多个点
那就换一个。注意到一条链上绝大多数点的度都是 \(2\) ,并且对一个数异或两次同样的值不会改变它。我们就可以想到:给点赋权为相邻的边的异或和。
这样一次操作就只会影响两个点的权。
那么我们的目标是什么呢?边权全 \(0\) 必然对应着点权全 \(0\) ,那么点权全 \(0\) 是否以为着满足要求呢?
该命题可以对子树运用归纳法,归纳的基础是叶节点满足性质,然后就可以方便地证明了。
所以我们的问题就变成了: 每次选定两个数,让它们同时异或上一个相同的值,最后使得点权全 \(0\) 。
首先,如果存在两个相同的数,我们肯定可以一次操作直接带走它们两个。
那么如果不存在两个相同的数呢?此时我们也肯定可以消去一个数。选定 \(x\) 和 \(y\) ,我们可以同时异或上 \(x\) ,于是就消掉了 \(x\) ,剩下了 \(x\oplus y\) 。
是否应该考虑一次操作不会消去数的情况呢?考虑这样操作后续的平均贡献必然 \(\le 1\) ,因而我们完全不需要考虑这种操作。
总结一下,我们现在的问题又变成了:
给定可重集 \(S\) ,每次可以进行如下操作之一:
1. 选出两个相同的数,并把它们删除。
2. 选出两个不同的数,把它们删除并加入它们的异或和。
求最小操作次数。
可以发现此时操作 1 限定了 \(S\) 中元素互不相同。而根据原题题意我们知道每个元素 \(\in[1,16)\) ,因此可以对集合直接进行状压 DP :
\(f(T)\):\(T\) 用于表示集合中数的存在情况,满足:\(T=\sum_{k=1}^{15} [k\in S]2^{(k-1)}\)。
然后就可以直接暴力转移了。需要注意的是,如果操作 2 新加入的数已存在于 \(S\) ,则需要再进行一次操作 1 。
可以发现这个转移以 \(\sum_k [k\in S]\) 划分阶段,因此最好使用记忆化搜索而不是状态枚举。
时间是 \(O(2^{15}\times 15^2)\) 。
代码
#include <cstdio>
#include <cstring>
const int INF = 0x3f3f3f3f;
const int MAXN = 1e5 + 5, MAXS = ( 1 << 16 ) + 5;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
template<typename _T>
_T MIN( const _T a, const _T b )
{
return a < b ? a : b;
}
int f[MAXS];
int w[MAXN];
int N, cnt;
bool vis[MAXS];
bool chk( const int S, const int b ) { return S >> b & 1; }
int DFS( const int S )
{
if( ! S ) return f[S] = 0;
if( vis[S] ) return f[S];
vis[S] = true;
for( int i = 1 ; i <= 15 ; i ++ )
for( int j = i + 1 ; j <= 15 ; j ++ )
if( chk( S, i - 1 ) && chk( S, j - 1 ) )
{
int t = S ^ ( 1 << i - 1 ) ^ ( 1 << j - 1 );
int v = i ^ j, nxt = t ^ ( 1 << v - 1 );
f[S] = MIN( f[S], DFS( nxt ) + 1 + chk( t, v - 1 ) );
}
return f[S];
}
int main()
{
read( N );
for( int i = 1, x, y, b ; i < N ; i ++ )
read( x ), read( y ), read( b ),
w[x + 1] ^= b, w[y + 1] ^= b;
int sta = 0, tot = 0;
for( int i = 1 ; i <= N ; i ++ )
if( w[i] )
tot += ( sta >> w[i] - 1 ) & 1,
sta ^= 1 << w[i] - 1;
memset( f, 0x3f, sizeof f );
write( DFS( sta ) + tot ), putchar( '\n' );
return 0;
}