并查集:有两个庞大的家族,每个家族里面关系都很复杂且明确。每个家族有唯一的一个族长,如果给出家族所有人之间的关系,要将所有的人归类到确定的家族(有什么意义?比如我知道a是b的姑妈,但是这并不知道a是哪个家族(A,B)的,如果知道a是A族长的女儿,那么间接就知道了b是A族长女儿的侄子,那么b就是A族的成员) 这就是并查集要做的第一件事情:根据元素之间的关系给元素分类,每一类元素都一个特有的标签(也就是一个族长)
第二件事情:如果将AB连两个族合并,不需要将A族的成员的全部转移到B族,只需要将A族族长和B族族长联系起来就可以。
#include
using namespace std;
const int N=100010;
int p[N]; //p[i]=j表示i和j有关系
int n,m;
int find(int x) //查询祖宗节点+路径压缩
{
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m;
int a,b;
for(int i=1;i<=n;i++) //初始化,一开始所有的元素都是一个集合
p[i]=i;
while(m--) //m次操作
{
char op;
cin>>op;
cin>>a>>b; //a,b有联系
if(op=='M') //合并a,b
{
p[find(a)]=find(b);
}
if(op=='Q') //查询
{
if(find(a)==find(b))puts("Yes");
else puts("No");
}
}
return 0;
}
#include
#include
using namespace std;
const int N=1e5+10;
int p[N];
int cnt[N];
int n,m;
int find(int x)
{
if(x!=p[x])p[x]=find(p[x]); //递归,回溯,回溯的时候路径压缩
return p[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) //初始化,一开始所有的元素都是一个集合
{
p[i]=i;
cnt[i]=1;
}
while(m--)
{
string op;
int a,b;
cin>>op;
if(op=="C") //a,b建立关系
{
cin>>a>>b;
int pa,pb;
pa=find(a);
pb=find(b);
if(pa!=pb)
{
p[pa]=pb;
cnt[pb]+=cnt[pa];
}
}
else if(op=="Q1") //判断a,b是否在一个集合
{
cin>>a>>b;
if(find(a)==find(b))puts("Yes");
else puts("No");
}
else
{
cin>>a;
cout<<cnt[find(a)]<<endl;
}
}
return 0;
}
大概思路:
带权并查集
将所有的关系都放入一个并查集,然后用d[n]来描述各个元素之间的关系。
即–建立关系,用d[n]判断关系。
食物链
#include
#include
using namespace std;
const int N=1e5+10;
int p[N];
int d[N]; //d[i]=j表示i到根节点的距离
int n,k;
int res=0; //记录假话条数
int find(int x)
{
if(x!=p[x])
{
int t=find(p[x]);
d[x]+=d[p[x]];
p[x]=t;
}
return p[x];
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
p[i]=i;
while(k--) //k句话
{
int D,a,b;
cin>>D>>a>>b;
if(a>n||b>n)
{
res++;
continue;
}
int pa=find(a);
int pb=find(b);
if(D==1) //a,b是同类
{
if(pa!=pb) //a,b不在一个集合,说明a,b的关系是第一次被提到,这句是真的
{
p[pa]=pb;
//d[a]+d[pa]=d[b]
d[pa]=d[b]-d[a];
}
else if(pa==pb&&(d[b]-d[a])%3!=0) //表示a,b关系之前已经给出且现在最新的一句话是假的
{
res++;
}
}
else //a吃b 即(d[a]+d[pa]-1)=d[b]
{
if(pa!=pb)
{
p[pa]=pb;
d[pa]=d[b]-d[a]+1;
}
else if(pa==pb)
{
if((d[a]-d[b]-1)%3!=0)res++;
}
}
}
cout<<res<<endl;
return 0;
}
#include
using namespace std;
const int N=200010;
int p[N];
int n,m,k;
int find(int x)
{
if(x!=p[x])p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++) //初始化并查集,所有元素都是一个集合
p[i]=i;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
int pa=find(a);
int pb=find(b);
p[pa]=pb;
}
cin>>k;
while(k--)
{
int a,b;
scanf("%d%d",&a,&b);
int pa=find(a);
int pb=find(b);
if(pa==pb)puts("Yes");
else puts("No");
}
return 0;
}
#include
#include
using namespace std;
const int N=30010;
int p[N];
int d[N]; //d[i]=j表示i到祖宗节点的距离
int sz[N]; //sz记录集合的点的个数
int n;
int find(int x)
{
if(x!=p[x])
{
int t=find(p[x]); //t表示x的祖宗节点
d[x]+=d[p[x]]; //回溯这句话,从祖宗节点开始回溯,那么d[x]会一直累加,1,2,3,4,,,
p[x]=t; //回溯的时候要用到还没更新的p[x],所以p[x]需要先保存,让d[x]完成使命再赋值给p[x]
}
return p[x];
}
int main()
{
cin>>n;
for(int i=0;i<N;i++)
p[i]=i,sz[i]=1;
while(n--)
{
char op;
int a,b;
cin>>op>>a>>b;
int pa=find(a);
int pb=find(b);
if(op=='M')
{
if(pa!=pb)
{
p[pa]=pb;
d[pa]+=sz[pb]; //pa到pb的距离+节点个数
sz[pb]+=sz[pa]; //pb列的节点个数
}
}
else
{
if(pa==pb)
{
cout<<max(0,abs(d[a]-d[b])-1)<<endl; //如果两个点相邻,那么他们中间没有点
}
else
cout<<"-1\n";
}
}
return 0;
}
对于堆的基础,推荐这篇博客:堆的简介
写的很基础,有基本的c基础都可以看懂。
堆是一个有特点的二叉树:对于任意一个节点,他的值大于等于他的所有孩子节点
对于堆的操作,简单介绍
插入元素:
将元素放入末尾,然后向上进行比较和转移,维护二叉树的性质(对于任意节点,他大于等于他的左右孩子节点)
弹出根节点:将最后一个节点的值赋给根节点,然后从根节点开始向下比较和转换,维护二叉树。
//此代码是上述博客的,非本人的
#include
using namespace std;
#define DEFAULT_CAPACITY 128
typedef struct _Heap {
int* arr; //存储堆元素的数组
int size; //当前已存储的元素个数
int capacity; //当前的存储容量
}Heap;
bool initHeap(Heap& heap, int* original, int size);
static void buildHeap(Heap& heap);
static void adjustDown(Heap& heap, int index);
static bool insertHeap(Heap& heap, int value);
static void adjustUp(Heap& heap, int index);
static bool popMax(Heap& heap, int& value);
//初始化堆
bool initHeap(Heap& heap, int* original, int size) {
//如果默认大小比size小,则申请默认大小的空间,否则申请size大小的空间
int capacity = DEFAULT_CAPACITY > size ? DEFAULT_CAPACITY : size;
heap.arr = new int[capacity];
if (!heap.arr)return false;
heap.capacity = capacity;
heap.size = 0;
//如果存在原始数据,则拷贝过来
if (size > 0) {
memcpy(heap.arr, original, size * sizeof(int));
heap.size = size;
buildHeap(heap);
}
return true;
}
/*从最后一个父节点开始(heap.size) - 1 / 2)(因为size是从1开始,所以要先减去1)
逐个调整所有的父节点(直到根节点),确保每一个父节点都是最大堆,最后
整体上形成一个最大堆*/
void buildHeap(Heap& heap) {
for (int i = (heap.size - 1) / 2; i >= 0; i--) {
adjustDown(heap, i);
}
}
void adjustDown(Heap& heap, int index) {
int cur = heap.arr[index]; //记录父节点值
int parent, child;
/*怕段是否存在大于当前结点的子节点,如果不存在,则堆本身平衡,不需要
调整,如果存在,则将最大子节点与之交换,交换后,如果这个子节点还有
子节点(即parent*2+1
for (parent = index; (parent * 2 + 1) < heap.size; parent = child) {
child = parent * 2 + 1; //左子节点
//取两个子节点最大结点
if (((child + 1) < heap.size) && (heap.arr[child + 1] > heap.arr[child])) {
child++;
}
if (cur >= heap.arr[child])break;//不大于,跳出循环
else {
/*大于当前父节点,进行交换,然后从子节点位置继续向下调整,
即for从第二次循环开始,初始值都为上一次的子节点位置*/
heap.arr[parent] = heap.arr[child];
heap.arr[child] = cur;
}
}
}
static bool popMax(Heap& heap, int& value);
bool popMax(Heap& heap, int& value) {
if (heap.size < 1)return false;
value = heap.arr[0];
//将size-1的位置值赋给根节点,size本身又--
/*相当于
heap.arr[0] = heap.arr[heap.size-1];
heap.size--;
*/
heap.arr[0] = heap.arr[--heap.size];
adjustDown(heap, 0); //向下执行堆调整
return true;
}
bool insertHeap(Heap& heap, int value) {
if (heap.size == heap.capacity) {
fprintf(stderr, "栈空间耗尽!\n");
return false;
}
int index = heap.size;
heap.arr[heap.size++] = value;//先赋值value,再size++
adjustUp(heap, index);
}
void adjustUp(Heap& heap, int index) {
if (index < 0 || index >= heap.size) {
//如果只有一个结点(插入的结点)inedx<0,或者大于堆的最大值,return掉
return;
}
int temp = heap.arr[index];//temp为插入的值
while (index > 0) {
int parent = (index - 1) / 2;
if (parent >= 0) { //如果索引没有出界,就执行想要操作
if (heap.arr[parent] < temp) {
heap.arr[index] = heap.arr[parent];
heap.arr[parent] = temp;
index = parent;
}
else break; //如果没有比父结点大,则跳出
}
else break; //越界,结束循环
}
}
int main() {
Heap hp;
int orignArry[] = { 1,2,3,87,93,82,92,86,95 };
if (!initHeap(hp, orignArry, sizeof(orignArry) / sizeof(orignArry[0]))) {
fprintf(stderr, "初始化堆失败!\n"); //输出到控制台
exit(-1);
}
for (int i = 0; i < hp.size; i++) {
cout << "第" << i << "个数为:" << hp.arr[i] << endl;
}
//向堆中插入元素
insertHeap(hp, 99);
printf("在堆中插入新的元素99,插入结果:\n");
for (int i = 0; i < hp.size; i++) {
cout << "第" << i << "个数为:" << hp.arr[i] << endl;
}
int value;
while (popMax(hp, value)) {
cout << "依次出列最大元素:" << value << endl;
}
system("pause");
return 0;
}
#include
#include
using namespace std;
const int N=1e6+10;
typedef long long ll;
ll h[N];
int n,m;
int hc;
void down(int father) //对father进行转移(向下)
{
int t=h[father];
int child=father*2+1;
//将当前节点的左右孩子节点与它进行比较,将最小的放到father的位置
if(child<=hc) //左孩子
{
if(h[child]<t)
t=h[child];
}
if(child+1<=hc)//右孩子
{
if(h[child+1]<t)
{
t=h[child+1];
child=child+1;
}
}
if(t<h[father]) //更新父亲节点
{
h[child]=h[father];
h[father]=t;
down(child); //由于child变动,所以继续调整
}
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++) //输入堆数组,但没有排序
cin>>h[i];
hc=n-1; //hc表示堆元素个数,必须在这里赋初值,否则down函数里面的hc是随机的
//要对堆排序,然后输出前面m个数
for(int i=(n-1)/2;i>=0;i--) //i*2+1表示i的左孩子,i*2+2表示i的右孩子,(i-1)/2表示i的父节点
//n/2表示最后一层有孩子的根节点中的最右边一个。
{
down(i);
}
while(m--)
{
cout<<h[0]<<" ";
//输入堆顶元素,然后要弹出继续更新
h[0]=h[hc]; //将堆最后一个元素赋值给堆顶元素,(相当于堆顶弹出)然后从堆顶开始更新
hc--;
down(0);
}
return 0;
}
#include
#include
using namespace std;
const int N=1e5+10;
int n,m=0;//输入需要操作的次数
int h[N],ph[N],hp[N],cnt;//h代表heap(堆),ph(point->heap)可以获得第几个插入的元素现在在堆的那个位置
//hp(heap->point)可以获得在堆的第n个元素存的是第几个插入的元素
void heap_swap(int a,int b){//交换在heap中位置分别为a,b的两个元素
swap(ph[hp[a]],ph[hp[b]]);//根据a和b的位置找到它们分别是第几个插入的元素,然后将其(在h数组中的)下标转换
swap(hp[a],hp[b]);//将两个位置存的是第几号元素转换
swap(h[a],h[b]);//最后再转换值(这三个语句位置可以换,但是从上到下逐渐变短的话比较美观)
}
void down(int u){//当前堆的元素下沉
int t=u;//让t代指u以及其两个儿子(三个点)中的最大值
if(u*2<=cnt and h[u*2]<h[t])t=u*2;
if(u*2+1<=cnt and h[u*2+1]<h[t])t=u*2+1;//注意此处为d[t]
if(u!=t){//最小值不是t,那么下沉,并且继续down操作
heap_swap(u,t);
down(t);
}
}
void up(int u){
while(u/2 and h[u/2]>h[u]){//第一个u/2是防止当u冲到顶然后陷入死循环
heap_swap(u/2,u);
u/=2;
}
}
int main(){
cin>>n;
while(n--){
string op;
int k,x;
cin>>op;
if(op=="I"){
cin>>x;
cnt++,m++;
ph[m]=cnt;//表示第m个插入的数的下标是cnt
hp[cnt]=m;//表示下标是cnt的数是第m个插入的数
h[cnt]=x;
up(cnt);
}else if(op=="PM"){//Print Min 打印最小
cout<<h[1]<<endl;
}else if(op=="DM"){
heap_swap(1,cnt);//将底部一个元素放上来
cnt--;//所有元素数量减一
down(1);//将放上来的元素沉下去
}else if(op=="D"){
cin>>k;//k存储拿到第几个输入的数字
k=ph[k];//k从储存第几个输入的数字变换为储存那个数字存放在h的哪个位置
heap_swap(k,cnt);//将底部一个元素放上来
cnt--;//所有元素数量减一
down(k);
up(k);
}else{//剩下来还没有操作的就是C(change)了,不必多谢一个if判断
cin>>k>>x;
k=ph[k];//k从储存第几个输入的数字变换为储存那个数字存放在h的哪个位置
h[k]=x;
down(k);
up(k);
}
}
return 0;
}
哈希表
a[x]=y
x叫关键字,y叫值。(key–value)
通过一系列的变换由x找到y
比如ax+by=c,则ax=c-by,x=(c-by)/a
哈希表有两个东西:
两种方法:拉链法和开放寻址法
两种方法的本质区别就是对于哈希冲突的解决。
什么是哈希冲突?
就是不同的x通过函数映射得到一样的y。
#include
#include
using namespace std;
const int N=1e5+10;
const int mod=1e5+7;
int h[N],e[N],ne[N],idx;
void insert(int x) //插入元素。
{
int k=(x%N+N)%mod;
e[idx]=x;
ne[idx]=h[k];
h[k]=idx++;
}
bool find(int x)
{
int k=(x%N+N)%mod;
for(int i=h[k];i!=-1;i=ne[i])
{
int j=e[i];
if(e[i]==x)
{
return true;
}
}
return false;
}
int main()
{
memset(h,-1,sizeof(h));
int n;
cin>>n;
while(n--) //n次操作
{
char op[2];
cin>>op;
int x;
cin>>x;
if(*op=='I')
insert(x);
else
{
if(find(x))puts("Yes");
else puts("No");
}
}
return 0;
}
#include
#include
#include
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5+5,P = 131;//131 13331
ULL h[N],p[N];
// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或 13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
ULL query(int l,int r)
{
return h[r] - h[l-1]*p[r-l+1];
}
int main()
{
int n,m;
cin>>n>>m;
string x;
cin>>x;
//字符串从1开始编号,h[1]为前一个字符的哈希值
p[0] = 1;
h[0] = 0;
for(int i=0;i<n;i++)
{
p[i+1] = p[i]*P;
h[i+1] = h[i]*P +x[i]; //前缀和求整个字符串的哈希值
}
while(m--)
{
int l1,r1,l2,r2;
cin>>l1>>r1>>l2>>r2;
if(query(l1,r1) == query(l2,r2)) printf("Yes\n");
else printf("No\n");
}
return 0;
}
#include
#include
#include
#include
using namespace std;
int base(string s, int b)
{
int res = 0;
for (auto x: s)
res = res * b + x - '0';
return res;
}
int main()
{
string x, y;
cin >> x >> y;
unordered_set<int> hash;
for (int i = 0; i < x.size(); i ++ )
{
string s = x;
s[i] ^= 1;
if (s.size() > 1 && s[0] == '0') continue;
hash.insert(base(s, 2));
}
for (int i = 0; i < y.size(); i ++ )
for (int j = 0; j < 3; j ++ )
if (y[i] - '0' != j)
{
string s = y;
s[i] = j + '0';
if (s.size() > 1 && s[0] == '0') continue;
int n = base(s, 3);
if (hash.count(n))
cout << n << endl;
}
return 0;
}