FZOJ 4108 世界树的考验

这题的转换非常的巧妙。

众所周知,树上一条路径点权异或有非常好的性质,可惜这道题是边权啊,这就需要一个转化:

  • 设每个点的点权为与它相连的所有边的边权的异或和。那么使得每个点的点权为(0)就是题目要求达到的状态。
    证明:考虑度数为(1)的点,显然与他们相连的边为(0),然后把他们删掉,又会有很多度数为(1)的点(类似于拓扑排序)……最后可以推出所有边都变成(0)

那么,每次操作把一条路径的每一条边异或同一个数(x),相当于把路径两端的点权都异或(x),其他点权都不变(大家可以自己考虑考虑)。

现在问题变成了,给你若干个数(数值 (leq) 15),每次给两个数异或上同一个数,问至少多少次可以把所有数变成(0)

显然相同的数直接异或掉就可以了,所以可以看成若干个不同的数。设(f[S])表示当状态为(S)时需要的最少操作数((i)属于集合(S)当且仅当 ((1<<i&S)!=0) ),其实就是一个状态压缩,然后发现状态总数非常少(只有(2^{16})种),所以可以直接一发记忆化搜索解决。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=100009,INF=1<<30;
int siz[N],point[N],n,S,f[N*10];

void init()
{
	scanf("%d",&n);
	int x,y,z;
	for (int i=1;i<n;i++)
	{
		scanf("%d %d %d",&x,&y,&z);
		point[x]^=z,point[y]^=z;
	}
	for (int i=0;i<=n;i++)
		siz[point[i]]++;
}

int dfs(int S)
{
	if(S==0)
		f[S]=0;
	if(f[S]!=INF)
		return f[S];
	for (int i=0;i<=15;i++)
		if(S&1<<i)
			for (int j=0;j<=15;j++)
				if(i!=j&&(S&1<<j))
				{
					int p=S^(1<<i)^(1<<j)^(1<<(i^j));
					if(S&(1<<(i^j)))
						f[S]=min(f[S],dfs(p)+2);
					else
						f[S]=min(f[S],dfs(p)+1);
				}
	return f[S];
}

void work()
{
	int ans=0;
	for (int i=1;i<=15;i++)
		ans+=siz[i]/2,siz[i]&1?(S^=(1<<i)):0;
	for (int i=0;i<1<<16;i++)
		f[i]=INF;
	printf("%d
",ans+dfs(S));
}

int main()
{
	init();
	work();
	return 0;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!
原文地址:https://www.cnblogs.com/With-penguin/p/12705843.html