牛客多校5 - Graph(字典树+分治求最小生成树)

题目链接:点击查看

题目大意:给出一棵树,每条边都有一个权值,每次操作可以删除任意一条边或者增加任意权值的一条边,现在可以执行数次操作,不过任何时间都要满足以下两个条件:

  1. n 个点互相连通
  2. 所有环的权值异或和为 0

求数次操作后图上边权之和的最小值

题目分析:将题意转换一下就可以转换为经典问题:完全图上的最小生成树,给出 n 个点,每个点都有权值 a[ i ] ,每条边的权值为 a[ i ] ^ a[ j ] 现在需要求最小生成树

这个题目只需要 dfs 跑一遍就可以转换为上述问题了,这里不多赘述,那么将问题转换后,该如何求解呢

有个 boruvka 算法也是求解最小生成树问题的,只不过是克鲁斯卡尔算法和普雷姆算法的结合版本,具体就是维护图中的所有连通块,然后贪心去找每一个连通块和其余所有的连通块之间的最小边权,将其合并为一个连通块,如此往复

那么和这个题目有什么联系呢?首先抛出一个问题:把当前图的点集随意划分成两半,递归两半后选出连接两个点集的边中权值最小的一条,得到最后的最小生成树。

乍一看没什么问题,但仔细想一下就会发现这个策略其实是错误的,因为最终的最小生成树中可能有两条连接当前层两个点集的边

但是对于本题而言,我们可以借助01字典树划分点集,借一下图:https://www.cnblogs.com/qieqiemin/p/13381095.html

牛客多校5 - Graph(字典树+分治求最小生成树)_第1张图片

当我们在字典树上从最高位到最低位维护每一个数字后,显然每一个节点的左右两个儿子,就已经将所有的点划分为两个集合了

牛客多校5 - Graph(字典树+分治求最小生成树)_第2张图片

以上图中的划分方法为例,假设两个集合是从第 deep 层的高度分开,显然两个集合中 deep 往上的位置在二进制下都是相同的,不同的只是 deep - 1 及往下的位置,所以合并上图中两个绿色集合中的点集所需要的最小代价就是:(1<

这样贪心由下往上去合并的话,上面那个错误的策略,在这个题目中的正确性得到了保证

综上所述,总结一下本题的求解方法,在计算出每个点的权值后,将其放入01字典树中,然后对于每一层深度分治即可,需要注意的几个地方就是,这个题目每个点的范围是 0 ~ n-1 ,而不是 1 ~ n,还有就是对于点权相同的点来说,我们可以想象在其之间建立一条边,因为花费为 0 ,所以不会对答案造成影响,故在处理时,对点权去重一下可以减少时间复杂度

至于实现,对于每次左右子树中的两个连通块,我是vector暴力维护的点集(向上传递完毕后不要忘记及时初始化,防止爆内存),然后遍历点集数更小的连通块,在另一个连通块中找异或值最小的答案,这个就是01字典树的基本应用了,所以字典树在本题中共有两个用途,一个是基本应用,也就是贪心去查找异或值最小的答案,另一个是结合最小生成树的贪心策略,将其分块处理

时间复杂度的话,因为字典树的高度为30,所以分治+vector暴力维护点集的时间复杂度为 nlogn,加上需要在字典树上查找异或的最小值,需要多加一个log,总的时间复杂度也就是 nlognlogn 了

代码打注释了,有不理解的地方看看代码应该就明白了

代码:
 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
 
typedef long long LL;
 
typedef unsigned long long ull;
 
const int inf=0x3f3f3f3f;

const int N=1e5+100;

struct Node
{
	int to,w;
	Node(int to,int w):to(to),w(w){}
};

vectornode[N];

vectorp[N*32],a;

int trie[N*32][2],cnt=1;

int newnode()//动态初始化
{
	cnt++;
	trie[cnt][0]=trie[cnt][1]=0;
	return cnt;
}

void insert(int x)//字典树的插入
{
	int pos=1;
	for(int i=30;i>=0;i--)
	{
		int to=(x>>i)&1;
		if(!trie[pos][to])
			trie[pos][to]=newnode();
		pos=trie[pos][to];
	}
	p[pos].push_back(x);
}

int search(int x,int pos,int deep)//字典树的查询,x:需要查询的值,pos:当前根节点,deep:深度
{
	int ans=0;
	for(int i=deep;i>=0;i--)
	{
		int to=(x>>i)&1;
		if(trie[pos][to])
			pos=trie[pos][to];
		else
		{
			pos=trie[pos][!to];
			ans|=(1<rs)
		swap(ls,rs);
	for(int i=0;i

 

你可能感兴趣的:(字符串处理,图论)