zoukankan      html  css  js  c++  java
  • [Atcoder] XOR Tree

    题目

    点这里看题目。

    分析

    奇奇妙妙的题目

    直接修改树上的路径会影响到很多条边,并不方便处理。我们需要压缩受影响信息的数量

    由于对点的处理更加灵活,因而我们考虑将边权转为点权

    考虑修改树上路径经常与树上差分挂钩,我们可以猜想第一种方法:给点赋权为它到根上所有边的权的异或和。

    但是没有什么用,因为一次操作仍然会影响到很多个点

    那就换一个。注意到一条链上绝大多数点的度都是 (2) ,并且对一个数异或两次同样的值不会改变它。我们就可以想到:给点赋权为相邻的边的异或和

    这样一次操作就只会影响两个点的权。

    那么我们的目标是什么呢?边权全 (0) 必然对应着点权全 (0) ,那么点权全 (0) 是否以为着满足要求呢?

    该命题可以对子树运用归纳法,归纳的基础是叶节点满足性质,然后就可以方便地证明了。


    所以我们的问题就变成了: 每次选定两个数,让它们同时异或上一个相同的值,最后使得点权全 (0)

    首先,如果存在两个相同的数,我们肯定可以一次操作直接带走它们两个。

    那么如果不存在两个相同的数呢?此时我们也肯定可以消去一个数。选定 (x)(y) ,我们可以同时异或上 (x) ,于是就消掉了 (x) ,剩下了 (xoplus y)

    是否应该考虑一次操作不会消去数的情况呢?考虑这样操作后续的平均贡献必然 (le 1) ,因而我们完全不需要考虑这种操作。

    总结一下,我们现在的问题又变成了:

    给定可重集 (S) ,每次可以进行如下操作之一:

    1. 选出两个相同的数,并把它们删除。

    2. 选出两个不同的数,把它们删除并加入它们的异或和。

    求最小操作次数。

    可以发现此时操作 1 限定了 (S) 中元素互不相同。而根据原题题意我们知道每个元素 (in[1,16)) ,因此可以对集合直接进行状压 DP :

    (f(T))(T) 用于表示集合中数的存在情况,满足:(T=sum_{k=1}^{15} [kin S]2^{(k-1)})

    然后就可以直接暴力转移了。需要注意的是,如果操作 2 新加入的数已存在于 (S) ,则需要再进行一次操作 1 。

    可以发现这个转移以 (sum_k [kin S]) 划分阶段,因此最好使用记忆化搜索而不是状态枚举。

    时间是 (O(2^{15} imes 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( '
    ' ); 
       return 0;
    }
    
  • 相关阅读:
    模拟http请求 带 chunked解析办法一
    DLL入口函数
    修复吾爱OD数据窗口双击不出现偏移问题
    PE导入表分析
    持仓盈亏公式
    hadoop工作相关
    zookeeper常用命令
    git使用命令行上传文件
    redis中各种数据类型对应的jedis操作命令
    volatile关键字比较好的解释
  • 原文地址:https://www.cnblogs.com/crashed/p/13392062.html
Copyright © 2011-2022 走看看