[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;
}
posted @ 2020-07-28 16:50  crashed  阅读(166)  评论(0编辑  收藏  举报