高效的存储和查找字符串集合的数据结构
int son[maxn][26],cnt[maxn],idx;
void add(char *str){///将新的字符串插入到字典树里
int p=0,len=strlen(str);
for(int i=0;i<len;i++){
///遍历字符串的每一个字母
int tmp=str[i]-'a';
if(!son[p][tmp]) son[p][tmp]=++idx;///没有节点的时候就创建节点
p=son[p][tmp];///往下走
}
cnt[p]++;///表示该地方有一个字符串
}
int qask(char *str){///查询某个字符串是否在字典树里出现过
int p=0,len=strlen(str);
for(int i=0;i<len;i++){
///逐位查找
int tmp=str[i]-'a';
if(!son[p][tmp]) return 0;///该节点不存在
p=son[p][tmp];///继续往下走
}
return cnt[p];///返回答案
}
题意:
在给定的n个整数里选出两个数进行异或运算,得到的结果最大是多少?
思路:
最基础的是要知道异或运算的含义,异或运算是一种位运算,简称“不进位加法”,在二进制下中逐位比较,若两位相同则为0,不同则为1。
思路一(暴力解法):
int maxx=0;
for(int i=1;i<=n;i++)///枚举第一个数
for(int j=1;j<i;j++)///枚举第二个数 寻找和a[i]异或值最大的数
maxx=max(maxx,a[i]^a[j]);
思路二(用Trie优化第二层循环):
首先要明确的一个问题就是在思路一中两层循环的作用,第一层循环是枚举第一个数x,第二层循环是寻找和第一个数异或值最大的数,然后再与最终结果取max。那么在这个过程中,我们可以利用贪心的思想,来将第二层循环省去。问题就转化成了如何找到和x异或值最大的数。
根据异或常见的思路,我们按位考虑,从最高位依次考虑,每次都优先考虑和x的当前位不同的数,这样肯定会比和x的当前位相同的数优,这样依次递推下去。
综上,我们可以构建01字典树,for循环枚举第一个数,再贪心求出第二个数,最后对答案取max即可。
int a[maxn],son[maxm][2],idx;
void add(int x){
///将数插入01字典树
int p=0;
for(int i=30;i>=0;i--){
///二进制拆分
int tmp=son[p][x>>i&1];
if(!tmp) tmp=++idx;///不存在就插入
p=s;///继续往下走
}
}
int Find(int x){///查找跟x异或值最大的数
int p=0,res=0;
for(int i=30;i>=0;i--){
int tmp=x>>i&1;
if(son[p][!tmp]){
res+=1<<i;
p=son[p][!tmp];
}
else p=son[p][tmp];
}
return res;
}
前言:
学完后才看到这个算法的,个人感觉先理解这个算法的思想会更容易点。
Borůvka算法用于求最小生成树,时间复杂度nlogn,可以将其看成多源的prim算法。
基本思路:
一开始的时候每个点都自成一个联通块,每次给所有联通块都找一条边权最小的边,其中一端在联通块内而另一段不在,加入这些边并合并联通块,重复直到没有联通块可合并。
证明和图示过程可以参考博客。
原题链接
题意:
给2e5个点,每个点都有点权,两个点如果连边,该边权就是两个点权的异或值,求所有点组成的最小生成树。
思路:
可以借鉴Borůvka算法的思路,初始的时候,把每个点都看做是一个联通块,这样我们进行Borůvka算法的过程,对于联通块每次都找一条边权最小的边,加入这些边合并联通块,重复直到没有联通块可合并。
本题跟普通的最小生成树就是边权的设置,对于上方的黑体字过程,首先想到的肯定就是暴力枚举的思路,但显然复杂度不够优秀。简化一下问题就是最小异或对,借鉴最大异或对(前置知识2)的思路,将点权都插入Trie树,这样从根节点rt到叶子节点的一条路径就表示一个点权。
根据贪心的思想,对于一个节点来说,如果左右子树都存在,就必须找到一条边要连接左右子树,那么这条边一定会有**W=1< 一个节点的最小异或生成树就是左子树的最小异或生成树+右子树的最小异或生成树+左右子树合并时的最小代价。对于前两个贡献可以递归进行计算,对于第三个贡献,就是两个子树的最小异或对+W。我们可以枚举任意一棵子树,借助Trie求异或另一颗子树的值,最后取min即可。 那么如何确定该子树里有哪些值呢?暴力dfs显然不可取,我们可以在插入前就排序,记录每个节点的左右区间,枚举的时候直接枚举区间里的数即可。 另外,借鉴并查集按秩合并的思想,可以在在合并子树时加一个小优化,每次都将小的子树合并到大的子树上,即启发式合并,可以优化几十毫秒吧。(已更正,感谢巨巨) 最后要注意数组开大一点! 代码: 原题链接 给一棵树,每条边都有边权,可以任意加边和删边,但要保证图联通并且任何一个环的边权异或和为0.求最小权值和。 思路: 首先要明确的一点就是无论添加的先后顺序如何,添加两点的边大小一定是固定的。因为要保证任何一个环的边权异或和为0;我们假设a[i]为根节点rt到i节点的所有边的异或和,所以如果节点x和节点y之间连边,该边的权值是a[x]^a[y]。 所以可以把确定的和不变的边权转化为各点点权,这样问题就转化成了求最小异或生成树。 对于黑字部分可以dfs求解即可。 具体证明 (证明超级详细的博客) 代码: https://ac.nowcoder.com/acm/contest/view-submission?submissionId=44469783 学长博客 (永远滴神) 可爱的巨巨 感觉很详细的一篇 也很详细的一篇 其他参考博客都在对应位置。#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
//#include
三、【牛客】2020牛客暑期多校训练营(第五场)B-graph (最小异或生成树)
题意:void dfs(int u,int fa,ll w){
a[u]=w;
for(int i=h[u];~i;i=e[i].ne){
int j=e[i].e;
if(j==fa) continue;
dfs(j,u,w^e[i].w);
}
}
四、杂七杂八
51Nod的相似题