题目链接:点击查看

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

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

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

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

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

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

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

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

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

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

以上图中的划分方法为例,假设两个集合是从第 deep 层的高度分开,显然两个集合中 deep 往上的位置在二进制下都是相同的,不同的只是 deep - 1 及往下的位置,所以合并上图中两个绿色集合中的点集所需要的最小代价就是:(1<<deep)+找到两个点 i 和 j ,满足点 i 属于左边的点集,j 属于右边的点集,同时 a[ i ] ^ a[ j ] 是最小的

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

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

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

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

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

代码:

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
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){}
};vector<Node>node[N];vector<int>p[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<<i);}}return ans;
}void dfs(int u,int fa,int val)
{a.push_back(val);for(auto it:node[u]){int v=it.to,w=it.w;if(v==fa)continue;dfs(v,u,val^w);}
}int query(int ls,int rs,int deep)//在ls的子树和rs的子树中找到权值最小的边
{int ans=INT_MAX;if(ls>rs)swap(ls,rs);for(int i=0;i<p[ls].size();i++)//暴力枚举大小较小的一个点集,logn去另一个点集中查询ans=min(ans,search(p[ls][i],rs,deep-1));return ans;
}LL solve(int rt,int deep)//分治,rt:根节点,deep:深度
{LL ans=0;int ls=trie[rt][0],rs=trie[rt][1];if(ls)//记录左子树中连通块的答案ans+=solve(ls,deep-1);if(rs)//记录右子树中连通块的答案ans+=solve(rs,deep-1);if(ls&&rs)//如果需要合并,找到异或和最小的边进行连接ans+=query(ls,rs,deep)+(1<<deep);p[rt].insert(p[rt].end(),p[ls].begin(),p[ls].end());//暴力维护点集p[rt].insert(p[rt].end(),p[rs].begin(),p[rs].end());p[ls].resize(0),p[rs].resize(0);//别忘了重置return ans;
}int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false); int n;scanf("%d",&n);for(int i=1;i<n;i++){int u,v,w;scanf("%d%d%d",&u,&v,&w);u++,v++;node[u].push_back(Node(v,w));node[v].push_back(Node(u,w));}dfs(1,-1,0);//将边权转换为点权sort(a.begin(),a.end());a.erase(unique(a.begin(),a.end()),a.end());//离散化去重for(auto v:a)//将不同的点插入到字典树中insert(v);printf("%lld\n",solve(1,30));return 0;
}

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

  1. 牛客多校5 - Interval(主席树)

    题目链接:点击查看 题目大意:给出一个长度为 n 的数列 a ,规定函数 f( l , r ) = a[ l ] & a[ l + 1 ] & ... & a[ r ] ,在规 ...

  2. 2019牛客多校第四场 I题 后缀自动机_后缀数组_求两个串de公共子串的种类数

    目录 求若干个串的公共子串个数相关变形题 对一个串建后缀自动机,另一个串在上面跑同时计数 广义后缀自动机 后缀数组 其他:POJ 3415 求两个串长度至少为k的公共子串数量 @(牛客多校第四场 I题 ...

  3. 2019牛客多校训练第十场F Popping Balloons

    2019牛客多校训练第十场F Popping Balloons 题意:二维平面内给你若干个点,然后你可以在x轴和y轴分别射三枪(每一枪的间隔是R),问最多能射掉多少气球. 题解:贪心.这个应该只能算作 ...

  4. python字符串去重及排序 牛客_2018牛客多校第一场 D.Two Graphs

    题意: n个点,m1条边的图E1,n个点,m2条边的图E2.求图E2有多少子图跟图E1同构. 题解: 用STL的全排列函数next_permutation()枚举映射.对于每一种映射枚举每一条边判断合 ...

  5. 2020 牛客多校第一场

    2020 牛客多校第一场 A. B-Suffix Array 后缀数组的思想:倍增+桶排序的方式找出一串连续序列后缀的大小.虽说正常使用的时候都是字典序,但是只要修改排序方式,也能够达到一个类似的&q ...

  6. 牛客多校三 B Black and white

    牛客多校三 B Black and white 在n*m的棋盘上,每个格子有一个数,初始可以选一定的格子标记为黑色,在任意四个形如(i1, j1)(i1, j2)(i2, j1)(i2, j2)的格子 ...

  7. 牛客小白9 换个角度思考(离线+树状数组)

    title: 牛客小白9 换个角度思考(离线+树状数组) date: 2018-11-29 15:25:18 tags: [离线,树状数组] categories: ACM 题目链接 题目描述 给定一 ...

  8. 2019牛客多校第一场

    2019牛客多校第一场 题号 题目 知识点 A Monotonic Matrix B Symmetric Matrix C Fluorescent 2 D Two Graphs E Removal F ...

  9. LCS(2021牛客多校4)

    LCS(2021牛客多校4) 题意: 让你构造三个字符串s1,s2,s3,长度均为n,要求LCS(s1,s2)=a,LCS(s2,s3)=b,LCS(s1,s3)=c 题解: 先考虑三个串互相LCS为 ...

最新文章

  1. 【GAN】GAN 也可以大幅压缩,来自MIT 韩松团队的最新研究!算力消耗不到1/9,现已开源!...
  2. PHP封装数据库连接
  3. the value of esp was not properly saved across a function call异常
  4. 【10.1】python中的GIL
  5. 三次元的世界里,机械臂的手活儿也无敌了
  6. 知识图谱嵌入:TransE算法原理及代码详解
  7. led伏安特性实验误差分析_大学物理实验伏安特性曲线的误差分析以及小结要怎么写,谢谢^ω^...
  8. latex:表格排版示例
  9. 压力变送器matlab,总结压差变送器三种不同故障以及处理方法[理论结合实际]
  10. tongweb7启动参数配置配置个人理解
  11. C语言程序设计-跳马问题
  12. Java基础 | 多态
  13. 直观理解Neural Tangent Kernel
  14. 【python】通过信号机制对子进程进行控制
  15. 《武道神尊》12.29上线链游玩家|放置挂机、轻松修真
  16. Mybatis报错:Could not resolve type alias
  17. 虚拟现实vr技术靠哪些设备进行空间定位
  18. Openwhisk之 -- 创建基于Docker的Action
  19. 学了java有必要学c语言吗_程序员有必要学习C语言吗?这几点原因很现实
  20. Androidstudio 配置阿里云镜像仓库

热门文章

  1. 网页上点击java没反应_JavaScript_javascript:void(0)点击登录没反应怎么解决,巧用批处理解决IE不支持JavaScri - phpStudy...
  2. 初始Docker-Docker和虚拟机的差别
  3. 认识微服务-服务架构演变
  4. 字节流写数据加异常处理
  5. SpringMVC快速入门-代码实现
  6. 类的加载过程三:Initialization
  7. AdminLTE介绍
  8. TCP通信的客户端代码实现
  9. show status用法
  10. SpringBoot集成其他技术-集成Redis