XOR Tree(AtCoder Petrozavodsk Contest 001 F)

XOR Tree

题目描述https://apc001.contest.atcoder.jp/tasks/apc001_f
题目来源 AtCoder Petrozavodsk Contest 001 F

题解

黄大爷(%%%hz)在校赛热(冷)身赛出了此题,本蒟蒻一点思路都没有。
后来发现是道atcoder上的原题,看了题解深感这是道好题呢。

首先题目说的是每次操作把树上的一条路径异或上一个值。
我们把边权换成点权,每个点的权值为与这个点相连的所有边的边权的异或和。
这样做的好处是每次修改操作就改成了在树上选连个节点并把这两个节点分别异或某个值。
为什么这样是对的呢?
对于一条路径上经过的每个节点,除了起点和终点外的其他节点都会与两条路径上的节点相连。
根据异或的性质,异或两次为原值。
所以对于一条路径在这条路径上把所有边都异或一个值等价于把这条路径的起点和终点异或这个值。

于是这道题就变成了,一棵树每次选两个点异或某值,求最少多少次使所有点值变为0。

题目有一个条件:数字的大小都小于16。
因此对于n个点,有很多点的点权是重复的。
对于两个点权相等的节点直接异或掉那个值可以一次性让两个节点变为0。
这样操作之后剩下的节点点权就都互不相同,且最多只有15个节点。

之后考虑状压dp,每次枚举两个值,把其中一个 i 异或掉另一个 j 可以去掉一个值。如果 i^j 在状态中出现过则可以把两个重复的值直接异或掉。
这样转移就好了。

根本想不到啊。
还是要提高一下思维水平诶。

代码

#include<bits/stdc++.h>
#define N 100005
#define M 65540
using namespace std;
int n,s[N],S,ans,f[M],inf,sum[20];

int dfs(int S)
{
  if(!S)return 0; 
  if(f[S]<inf)return f[S];
  for(int i=0;i<16;i++)if((S>>i)&1)
    for(int j=0;j<16;j++)if(i!=j&&(S>>j)&1)
    {
      int p=i^j,x=S^(1<<i)^(1<<j)^(1<<p);
      if(S>>p&1)f[S]=min(f[S],dfs(x)+2);
      else f[S]=min(f[S],dfs(x)+1);
      //枚举每次异或的两个节点i,j,i异或掉j,j异或掉j,还剩下p=i^j
      //如果p本身存在那两个p直接消掉 
    }
  return f[S];
}

int main()
{
  int a,b,c;
  scanf("%d",&n);
  for(int i=1;i<n;i++)
    scanf("%d%d%d",&a,&b,&c),s[a]^=c,s[b]^=c;
  for(int i=0;i<n;i++)sum[s[i]]++;
  for(int i=1;i<16;i++)
    ans+=(sum[i]>>1),S+=(1<<i)*(sum[i]&1);
  memset(f,127,sizeof(f));inf=f[0];
  printf("%d\n",ans+dfs(S));
  return 0;
} 

猜你喜欢

转载自blog.csdn.net/wcy_1122/article/details/79775081