1.Tire树
2. 并查集
3. 模拟堆(可对第k个元素进行相关操作)
4. Hash表
一 :Trie树(字典树)
① : 树形结构,hash树变种,典型应用是用于统计和排序大量字符串(不仅限于字符串)
② : 优点:最大限度地减少无所谓的字符串比较,查询效率大于hash表
核心思想-----空间换取时间(一般需要多开数组),利用公共前缀来降低查询时间的开销从而达到提高效率的目的。
二: 三个性质:
①根节点不包含字符,除了根节点外每个节点都只包含一个字符。
②从根节点到某一节点,路径上经过的字符串连接起来为该节点对应的字符串。
③每个结点的所有子节点包含的字符都不相同。
–假设有 b,abc,abd,bcd,abcd,efg ,hi这六个单词,构建的树就如上图,从第一个单词开始确定有无这个字符开头的单词,若存在,便继续第二个单词往下找,其他也是这样,如此一来,原本的朴素算法便会少去许多层的搜索。
经典类型例题①:
给出1e5个长度不超k的单词,对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置/出现了几次。
基本思路:
对于一个单词从根节点遍历到该单词最后一个字符的过程就是一个单词,然后对最后一个单词进行标记,表示存在,而后查找的时候若是没有存在标记便是不存在。若需插入这个不存在的单词只需在这个未曾标记过的地方标记上即可。
时间复杂度:len作为插入的字符串的长度,O(len)为插入一个的时间复杂度,查询一个单词的时间复杂度还是O(len),因此插入和查询的总时间复杂度应该都在O(n*len)左右。
经典类型例题之模板题之一:
AcWing835. Trie字符串统计
AC 代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int son[N][26];//数组模拟节点
int cnt[N];//记录到该节点结束的单词数量
int idx;//表示第idx个插入的点
char str[N];
void insert(char *str)//插入字符出
{
int p=0;
for (int i=0;str[i];i++)
{
int u=str[i]-'a';
if(!son[p][u])son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;//记录下该点结束的字符串个数
}
int query(char *str)//返回字符串个数
{
int p=0;
for (int i=0;str[i];i++)
{
int u=str[i]-'a';
if (!son[p][u]) return 0;
p=son[p][u];
}
return cnt[p];
}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >>n;
string oper;
while (n--)
{
cin>>oper>>str;
if(oper=="I") insert(str);
else cout<<query(str)<<endl;
}
return 0;
}
经典例题②
求异或对:
AcWing143.最大异或对
little tips:当两个数的二进制从最高位向最低位比较的时候,不同的地方在越前面 xor运算 的和越大
AC代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010,M=31*N;//M表示所有个数的位数假期时总共有多少个(用数组模拟的话只要要放下每个数的每一位才行)
int son[M][2];
int a[N];
int n;
int idx;
void insert(int a)
{
int p=0;
for (int i=30;i>=0;i--)
{
int u=a>>i&1;
if (!son[p][u])son[p][u]=++idx;
p=son[p][u];
}
}
int search(int a)
{
int p=0, res=0;
for (int i=30;i>=0;i--)
{
int u=a>>i&1;
if (son[p][!u])
{
p=son[p][!u];
res=res*2+1;
}
else
{
p=son[p][u];
res=res*2+0;
}
}
return res;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
cin>>n;
idx=0;
for (int i=1;i<=n;i++)
{
cin>>a[i];
insert(a[i]);
}
int maxm=0;
for (int i=1;i<=n;i++)
{
maxm=max(maxm,search(a[i]));
}
cout<<maxm<<endl;
return 0;
}
做的难度比之前两道题难度稍高的题:
洛谷P5149
题目思路大概可以分成两部分:
①字符串trie树储存,同时插入记下顺序
②更新后的座位表通过trie树查询原先顺序,同时记录在数组中
③用归并算逆序数对
AC代码
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int MAXN = 1e5 + 5;
int p[5*MAXN][26 * 2], cnt[5*MAXN];
int n, idx = 1;
int numla[MAXN], b[MAXN];
char s[6];
typedef long long ll;
ll MergeSort(int l, int r) {
if (l == r) return 0;
int mid = (l + r) >> 1;
ll ans=0;
ans+=MergeSort(l, mid)+MergeSort(mid + 1, r);
int i = l, j = mid + 1, t = l;
while (i <= mid && j <= r)
if (numla[i] <= numla[j])
b[t++] = numla[i++];
else
b[t++] = numla[j++], ans += mid - i + 1;
while (i <= mid) b[t++] = numla[i++];
while (j <= r) b[t++] = numla[j++];
for (int k = l; k <= r; ++k) numla[k] = b[k];
return ans;
}
void insert(char s[], int k) {
int u = 1;
for (int i = 0; i < strlen(s); i++) {
int a = s[i] - 'a';
if (!p[u][a]) p[u][a] = ++idx;
u = p[u][a];
}
cnt[u] = k;
return;
}
void search(char s[], int k) {
int u = 1;
for (int i = 0; i < strlen(s); i++) {
int a = s[i] - 'a';
u = p[u][a];
}
numla[k] = cnt[u];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", s);
insert(s, i);
}
for (int i = 1; i <= n; i++) {
scanf("%s", s);
search(s, i);
}
cout << MergeSort(1, n);
}
(trie树刚入门就放些简单的题打卡了)
二:并查集
(听说是经常作为辅助手段,就做了点模板题入门,因此就放链接和代码了)
模板一:集合的合并与查询
AcWing836
#include<iostream>
using namespace std;
const int N=1e5+10;
int p[N];
int find(int x)//路径压缩法
{
if (p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int main()
{
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for (int i=1;i<=n;i++)p[i]=i;
while (m--)
{
string oper;
int a,b;
cin>>oper;
cin>>a>>b;
if(oper=="M") p[find(a)]=find(b);
else
if(find(a)==find(b))cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
模板题的进阶版本:
AcWing 837
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int p[N];
int cnt[N];//记录一次为节点的点的个数
int find (int x)
{
if (p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for (int i=1;i<=n;i++)
{
cnt[i]=1;
p[i]=i;
}
while (m--)
{
string oper;
cin>>oper;
if (oper=="C")
{
int a,b;
cin>>a>>b;
if (find(a)==find(b))continue;
cnt[find(b)]+=cnt[find(a)];
p[find(a)]=find(b);
}
else if(oper=="Q1")
{
int a,b;
cin>>a>>b;
if(find(a)!=find(b))cout<<"No"<<endl;
else cout<<"Yes"<<endl;
}
else
{
int a;
cin>>a;
cout<<cnt[find(a)]<<endl;
}
}
return 0;
}
接下来这道题感觉有那么亿点点难…(毕竟我刚学不是)
AcWing 240 食物链
刚开始我的思路是简单的像模板那样,合并集合维护集合,后来发现没有办法表示出父子节点之间的关系,而且给的数据并不是连在一起的,所以不同编号的动物在最终的集合中不一定能够表示到同一个类别上去,而是有相对的一个三角链关系。于是初步的思路出来了:用在寻找祖宗节点的时候维护一个数组表示d[x]表示x这个点到其祖宗节点的距离:d[x]+=d[p[x]]再用递归实现,而后在之后的计算中对其进行3的取余,0分别表示同类点,1表示子节点动物吃祖宗节点动物,2表示祖宗节点动物吃该子节点动物。不久又发现问题,合并集合的时候必然有一方的祖宗节点要变成另一方的祖宗节点的子节点,那么祖宗节点和子节点之间的距离就要用两个比较的点的d[x]来计算两集合祖宗节点之间的距离(吃与被吃)的关系。于是有了如下AC代码
#include<iostream>
using namespace std;
const int M=5e4+10;
int N,K;
int p[M],d[M];//放种类
int find(int x)
{
if(p[x]!=x)
{
int u=find(p[x]);
d[x]+=d[p[x]];
p[x]=u;
}
return p[x];
}
int main()
{
ios::sync_with_stdio(false);
cin>>N>>K;
for (int i=1;i<=N;i++) p[i]=i;
int res=0;
while(K--)
{
int D,x,y;
cin>>D>>x>>y;
if (x>N||y>N) res++;
else
{
int px=find(x),py=find(y);
if (D==1)
{
if(px==py&&(d[x]-d[y])%3) res++;
else if(px!=py)
{
p[px]=py;
d[px]=d[y]-d[x];//更新距离
}
}
else
{
if(px==py&&(d[x]-d[y]-1)%3)res++;
else if(px!=py)
{
p[px]=py;
d[px]=d[y]-d[x]+1;
}
}
}
}
cout<<res<<endl;
return 0;
}
并查集暂告一段落,等我学更多算法了再去做难一些的题
模拟堆和hash我下次补上
21.3.28