一、trie
1、835. Trie字符串统计
题意:
//son[p][u]分别表示p节点的u个孩子
#include<iostream>
using namespace std;
const int N=1e5+10;
int son[N][26],cnt[N],idx;//cnt存的是每个字符串出现的次数,idx下标是0的点,即是根节点,也是空节点
void insert(char str[])
{
int p=0;//指针,指向下一个要查询的节点
for(int i=0;str[i];i++)//str[i]等同于str[i]!='\n'
{
int u=str[i]-'a';
if(!son[p][u])
son[p][u]=++idx;//p节点的子节点中不存在这个值,就再扩展一个节点,于是idx+1
p=son[p][u];//p指向的节点进行移动
}
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()
{
int n;
char op[2],str[N];
scanf("%d",&n);
while(n--)
{
scanf("%s%s",&op[0],str);
if(op[0]=='I')
{
insert(str);
}
else
printf("%d\n",query(str));
}
return 0;
}
143. 最大异或对
思路:先把每个数存在同一棵trie树上,后面再让每一个数在trie树上找与它异或之后值最大的一个。因为每个节点只有0或1两种可能,所以在找的时候直接找当前节点的子节点与它相反的数然后往这个节点走,如果没有存在与它相反的数,那就只能往这个与它一样的数的节点走了。总的想法:每次从最高位考虑,尽量使最高位异或之后的结果是1,就可以保证最后的结果一定是最大的。
#include<iostream>
using namespace std;
const int N=1e5+10,M=N*31;//N有多少个数,M所有数可能的总长度
int son[M][2],idx;//son每个节点最多只有两个子节点0或1
int a[N];
void insert(int x)
{
int p=0;
for(int i=30;i>=0;i--)
{
int u=(x>>i)&1;//获得当前位的二进制
if(!son[p][u])
son[p][u]=++idx;
p=son[p][u];//指针移动到下一个结点
}
}
int query(int x)
{
int p=0,res=0;
for(int i=30;i>=0;i--)
{
int u=(x>>i)&1;//0或1
if(son[p][!u])//如果存在与当前位相反的,则往这条路走
{
p=son[p][!u];
res=(res<<1)+1;//res左移一位,异或不同加1。注意优先级!!!
}
else
{
p=son[p][u];//没有存在不一样的,只能走这条路了
res=(res<<1)+0;
}
}
return res;
}
int main()
{
int n,ans=0;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
insert(a[i]);//用trei把每个数先建立起来
}
//直接查找,前面已经把所有数建在一棵trei树上了,接下来只需要把每个数传进去,让它从
//根节点开始查找
for(int i=0;i<n;i++)
{
ans=max(ans,query(a[i]));
}
cout<<ans<<endl;
return 0;
}
二、并查集
并查集:将两个原本不相交的集合合并成一个,通过间接的关系使满足一定关系的集合全部在同一个集合下,使它们有一个公共的根节点。
①合并:将两个不相交的集合(点)合并为在同一个集合下,在同一个集合的所有元素它们的父节点的值都指向根节点,所以同一个集合下所有元素的父节点一定相同。
②查询:通过之前合并之后可以查询两个任意元素是否属于同一集合。
通过把所有的节点的父节点的值都指向同一个根节点,可以很方便的进行查询和合并。
③应用:亲戚,是否能构成回路等。
核心:找每个集合的根节点
int find(int x)//找每个节点的祖宗节点
{
if(x!=fa[x])
fa[x]=find(fa[x]);//路径压缩
return fa[x];
}
1、https://www.acwing.com/problem/content/839/
思路:并查集模板外,怎么找每一个点的联通点数量。用一个数组来记录每一个点的祖宗结点的联通点的数量,每次在合并的时候把要连接到新的祖宗结点的那个点的联通点数量加到祖宗结点上,注意不要把已经在同一集合下的点在来连接一遍了,所以要先判断他们的祖宗结点是不是同一个find(a)==find(b)?最后算每个节点的联通点数量时要先找到它的祖宗节点,再输出祖宗节点的联通点数量cnt[find(a)]。
#include<iostream>
#include<string>
using namespace std;
const int N=1e5+10;
int fa[N],count[N];
int find(int x)
{
if(x!=fa[x])
fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
fa[i]=i;
count[i]=1;//记录联通点的数量
}
while(m--)
{
int a,b;
char op[3];
scanf("%s",op);
if(op[0]=='C')
{
scanf("%d%d",&a,&b);
int x=find(a),y=find(b);
if(x!=y)//防止已经连接过的再连接,否则联通点的数会重复计算!!!
{
fa[x]=y;
count[y]+=count[x];//count里存的是每个节点的祖宗节点联通的数量
}
}
else if(op[1]=='1')
{
scanf("%d%d",&a,&b);
if(find(a)==find(b))
printf("Yes\n");
else
printf("No\n");
}
else
{
scanf("%d",&a);
printf("%d\n",count[find(a)]);//找a的祖宗节点联通点的数量
}
}
return 0;
}
三、堆:是一棵完全二叉树
小根堆:每个节点的值一定小于等于它的子节点,所以最后根节点的值是整棵树中最小的。
几个基本操作:
1、插入最小值:插入操作,都是把元素插入到最后一个位置的,然后再up。
2、求集合最小值:直接输出根节点。
3、删除集合最小值:删除操作,是用最后一个元素来取代被删除元素的位置,再从删除元素的位置down。
4、删除任一元素
5、修改任一元素
1、堆排序
#include<iostream>
using namespace std;
const int N=1e5+10;
int h[N],s;
void down(int u)
{
int t=u;
if(u*2<=s&&h[u*2]<h[t])
t=2*u;
if(u*2+1<=s&&h[u*2+1]<h[t])
t=2*u+1;
if(t!=u)
{
swap(h[u],h[t]);
down(t);
}
}
void up(int u)
{
while(u)
{
if(u/2&&h[u/2]>h[u])
swap(h[u/2],h[u]);
u=u/2;
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]);
s=n;
for(int i=n/2;i;i--)//从n/2开始,细品!
down(i);
while(m--)
{
printf("%d ",h[1]);
h[1]=h[s];//删除顶点,用最后一个点来覆盖顶点,再down
s--;
down(1);
}
return 0;
}
2、839. 模拟堆
思路:这里有一个很绕的地方,题目要求的是第k个插入的数,所以需要记录第k个插入的元素在堆里的下标是多少,下标为i的元素是第几个插入的,然后在交换元素的时候同时把这两个一起交换了。
#include<iostream>
using namespace std;
const int N=1e5+10;
int h[N],ph[N],hp[N];//ph存放第k个插入的点的下标,hp存放下标i是第几个插入的
int len;
void heap_swap(int x,int y)//细品
{
swap(hp[x],hp[y]);
swap(ph[hp[x]],ph[hp[y]]);
swap(h[x],h[y]);
}
void down(int u)
{
int t=u;
if(u*2<=len&&h[u*2]<h[t])
t=u*2;
if(u*2+1<=len&&h[u*2+1]<h[t])
t=u*2+1;
if(t!=u)
{
heap_swap(t,u);
down(t);
}
}
void up(int u)
{
while(u)
{
if(u/2&&h[u/2]>h[u])
heap_swap(u/2,u);
u=u/2;
}
}
int main()
{
int n,m=0;
cin>>n;
while(n--)
{
char op[3];
int x,k;
cin>>op;
if(op[0]=='I')//插入从最后一个位置插入
{
cin>>x;
m++;
h[++len]=x;
ph[m]=len;//第m个插入的数在堆里的下标是多少
hp[len]=m;//堆里下标len是第几个插入的
up(len);
}
else if(op[0]=='P')
{
cout<<h[1]<<endl;
}
else if(op[0]=='D'&&op[1]=='M')
{
heap_swap(1,len);
len--;
down(1);
}
else if(op[0]=='D')
{
cin>>k;
k=ph[k];//第k个插入的数的下标
heap_swap(k,len);
len--;
down(k),up(k);
}
else
{
cin>>k>>x;
k=ph[k];
h[k]=x;
down(k),up(k);
}
}
return 0;
}