单链表

实现一个单链表,链表初始为空,支持三种操作:

  1. 向链表头插入一个数;
  2. 删除第 kk 个插入的数后面的数;
  3. 在第 kk 个插入的数后插入一个数。

现在要对该链表进行 MM 次操作,进行完所有操作后,从头到尾输出整个链表。

注意:题目中第 kk 个插入的数并不是指当前链表的第 kk 个数。例如操作过程中一共插入了 nn 个数,则按照插入的时间顺序,这 nn 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 nn 个插入的数。

输入格式

第一行包含整数 MM,表示操作次数。

接下来 MM 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. H x,表示向链表头插入一个数 xx。
  2. D k,表示删除第 kk 个插入的数后面的数(当 kk 为 00 时,表示删除头结点)。
  3. I k x,表示在第 kk 个插入的数后面插入一个数 xx(此操作中 kk 均大于 00)。

输出格式

共一行,将整个链表从头到尾输出。

数据范围

1≤M≤1000001≤M≤100000
所有操作保证合法。

输入样例:

10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6

输出样例:

6 4 6 5
#include <iostream>using namespace std;const int N = 100010;int n;
int h[N], e[N], ne[N], head, idx;//对链表进行初始化
void init(){head = -1;//最开始的时候,链表的头节点要指向-1,//为的就是在后面进行不断操作后仍然可以知道链表是在什么时候结束/*插句题外话,我个人认为head其实就是一个指针,是一个特殊的指针罢了。刚开始的时候它负责指向空结点,在链表里有元素的时候,它变成了一个指向第一个元素的指针当它在初始化的时候指向-1,来表示链表离没有内容。*/idx = 0;//idx在我看来扮演两个角色,第一个是在一开始的时候,作为链表的下标,让我们好找//第二在链表进行各种插入,删除等操作的时候,作为一个临时的辅助性的所要操作的元素的下//标来帮助操作。并且是在每一次插入操作的时候,给插入元素一个下标,给他一个窝,感动!/*再次插句话,虽然我们在进行各种操作的时候,元素所在的下标看上去很乱,但是当我们访问的时候,是靠着指针,也就是靠ne[]来访问的,这样下标乱,也就我们要做的事不相关了。另外,我们遍历链表的时候也是这样,靠的是ne[]*/
}
//将x插入到头节点上
void int_to_head(int x){//和链表中间插入的区别就在于它有head头节点e[idx] = x;//第一步,先将值放进去ne[idx] = head;//head作为一个指针指向空节点,现在ne[idx] = head;做这把交椅的人换了//先在只是做到了第一步,将元素x的指针指向了head原本指向的head = idx;//head现在表示指向第一个元素了,它不在是空指针了。(不指向空气了)idx ++;//指针向下移一位,为下一次插入元素做准备。
}//将x插入到下标为k的点的后面
void add(int k, int x){e[idx] = x;//先将元素插进去ne[idx] = ne[k];//让元素x配套的指针,指向它要占位的元素的下一个位置ne[k] = idx;//让原来元素的指针指向自己idx ++;//将idx向后挪/*为了将这个过程更好的理解,现在将指针转变的这个过程用比喻描述一下,牛顿老师为了省事,想插个队,队里有两个熟人张三和李四,所以,他想插到两个人中间,但是三个人平时关系太好了,只要在一起,就要让后面的人的手插到前面的人的屁兜里。如果前面的人屁兜里没有基佬的手,将浑身不适。所以,必须保证前面的人屁兜里有一只手。(张三在前,李四在后)这个时候,牛顿大步向前,将自己的手轻轻的放入张三的屁兜里,(这是第一步)然后,将李四放在张三屁兜里的手抽出来放到自己屁兜里。(这是第二步)经过这一顿骚操作,三个人都同时感觉到了来自灵魂的战栗,打了个哆嗦。*/
}//将下标是k的点后面的点个删掉
void remove(int k){ne[k] = ne[ne[k]];//让k的指针指向,k下一个人的下一个人,那中间的那位就被挤掉了。
}
int main(){cin >> n;init();//初始化for (int i = 0; i < n; i ++ ) {char s;cin >> s;if (s == 'H') {int x;cin >> x;int_to_head(x);}if (s == 'D'){int k;cin >> k;if (k == 0) head = ne[head];//删除头节点else remove(k - 1);//注意删除第k个输入后面的数,那函数里放的是下标,k要减去1}if (s == 'I'){int k, x;cin >> k >> x;add(k - 1, x);//同样的,第k个数,和下标不同,所以要减1}}for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ' ;cout << endl;return 0;
}作者:大海呀大海
链接:https://www.acwing.com/solution/content/16251/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

双链表

实现一个双链表,双链表初始为空,支持 55 种操作:

  1. 在最左侧插入一个数;
  2. 在最右侧插入一个数;
  3. 将第 kk 个插入的数删除;
  4. 在第 kk 个插入的数左侧插入一个数;
  5. 在第 kk 个插入的数右侧插入一个数

现在要对该链表进行 MM 次操作,进行完所有操作后,从左到右输出整个链表。

注意:题目中第 kk 个插入的数并不是指当前链表的第 kk 个数。例如操作过程中一共插入了 nn 个数,则按照插入的时间顺序,这 nn 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 nn 个插入的数。

输入格式

第一行包含整数 MM,表示操作次数。

接下来 MM 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. L x,表示在链表的最左端插入数 xx。
  2. R x,表示在链表的最右端插入数 xx。
  3. D k,表示将第 kk 个插入的数删除。
  4. IL k x,表示在第 kk 个插入的数左侧插入一个数。
  5. IR k x,表示在第 kk 个插入的数右侧插入一个数。

输出格式

共一行,将整个链表从左到右输出。

数据范围

1≤M≤1000001≤M≤100000
所有操作保证合法。

输入样例:

10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2

输出样例:

8 7 7 3 2 9
#include<iostream>
using namespace std;
const int N=1e5+10;
int m;
int e[N],l[N],r[N],idx;
void init(){l[1]=0,r[0]=1;idx=2;
}
void add(int k,int x){e[idx]=x;l[idx]=k;r[idx]=r[k];l[r[k]]=idx;r[k]=idx;idx++;
}
void remove(int k){r[l[k]]=r[k];l[r[k]]=l[k];
}
int main(void){
cin>>m;
init();
while(m--){string op;cin>>op;int k,x;if(op=="R"){cin>>x;add(l[1],x);}else if(op=="L"){cin>>x;add(0,x);}else if(op=="D"){cin>>k;remove(k+1);}else if(op=="IL"){cin>>k>>x;add(l[k+1],x);        }else{cin>>k>>x;add(k+1,x);}}
for(int i=r[0];i!=1;i=r[i]){cout<<e[i]<<" ";
}   return 0;   }

实现一个栈,栈初始为空,支持四种操作:

  1. push x – 向栈顶插入一个数 xx;
  2. pop – 从栈顶弹出一个数;
  3. empty – 判断栈是否为空;
  4. query – 查询栈顶元素。

现在要对栈进行 MM 个操作,其中的每个操作 33 和操作 44 都要输出相应的结果。

输入格式

第一行包含整数 MM,表示操作次数。

接下来 MM 行,每行包含一个操作命令,操作命令为 push xpopemptyquery 中的一种。

输出格式

对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。

其中,empty 操作的查询结果为 YES 或 NOquery 操作的查询结果为一个整数,表示栈顶元素的值。

数据范围

1≤M≤1000001≤M≤100000,
1≤x≤1091≤x≤109
所有操作保证合法。

输入样例:

10
push 5
query
push 6
pop
query
pop
empty
push 4
query
empty

输出样例:

5
5
YES
4
NO
#include <iostream>
using namespace std;
const int N = 100010;
int st[N];
int top = -1;
int n;
int main()
{cin >> n;while(n--){string s;cin >> s;//栈顶所在索引往后移动一格,然后放入x。if(s == "push"){int a;cin >> a;st[++top] = a;}//往前移动一格if(s == "pop"){top --;}//返回栈顶元素if(s == "query"){cout << st[top] << endl;}//大于等于 0 栈非空,小于 0 栈空if(s == "empty"){cout << (top == -1 ? "YES" : "NO") << endl;}}
}

给定一个表达式,其中运算符仅包含 +,-,*,/(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。

注意:

  • 数据保证给定的表达式合法。
  • 题目保证符号 - 只作为减号出现,不会作为负号出现,例如,-1+2,(2+2)*(-(1+1)+2) 之类表达式均不会出现。
  • 题目保证表达式中所有数字均为正整数。
  • 题目保证表达式在中间计算过程以及结果中,均不超过 231−1231−1。
  • 题目中的整除是指向 00 取整,也就是说对于大于 00 的结果向下取整,例如 5/3=15/3=1,对于小于 00 的结果向上取整,例如 5/(1−4)=−15/(1−4)=−1。
  • C++和Java中的整除默认是向零取整;Python中的整除//默认向下取整,因此Python的eval()函数中的整除也是向下取整,在本题中不能直接使用。

输入格式

共一行,为给定表达式。

输出格式

共一行,为表达式的结果。

数据范围

表达式的长度不超过 105105。

输入样例:

(2+2)*(1+1)

输出样例:

8
#include <iostream>
#include <stack>
#include <string>
#include <unordered_map>
using namespace std;stack<int> num;
stack<char> op;//优先级表
unordered_map<char, int> h{ {'+', 1}, {'-', 1}, {'*',2}, {'/', 2} };void eval()//求值
{int a = num.top();//第二个操作数num.pop();int b = num.top();//第一个操作数num.pop();char p = op.top();//运算符op.pop();int r = 0;//结果 //计算结果if (p == '+') r = b + a;if (p == '-') r = b - a;if (p == '*') r = b * a;if (p == '/') r = b / a;num.push(r);//结果入栈
}int main()
{string s;//读入表达式cin >> s;for (int i = 0; i < s.size(); i++){if (isdigit(s[i]))//数字入栈{int x = 0, j = i;//计算数字while (j < s.size() && isdigit(s[j])){x = x * 10 + s[j] - '0';j++;}num.push(x);//数字入栈i = j - 1;}//左括号无优先级,直接入栈else if (s[i] == '(')//左括号入栈{op.push(s[i]);}//括号特殊,遇到左括号直接入栈,遇到右括号计算括号里面的else if (s[i] == ')')//右括号{while(op.top() != '(')//一直计算到左括号eval();op.pop();//左括号出栈}else{while (op.size() && h[op.top()] >= h[s[i]])//待入栈运算符优先级低,则先计算eval();op.push(s[i]);//操作符入栈}}while (op.size()) eval();//剩余的进行计算cout << num.top() << endl;//输出结果return 0;
}

队列

实现一个队列,队列初始为空,支持四种操作:

  1. push x – 向队尾插入一个数 xx;
  2. pop – 从队头弹出一个数;
  3. empty – 判断队列是否为空;
  4. query – 查询队头元素。

现在要对队列进行 MM 个操作,其中的每个操作 33 和操作 44 都要输出相应的结果。

输入格式

第一行包含整数 MM,表示操作次数。

接下来 MM 行,每行包含一个操作命令,操作命令为 push xpopemptyquery 中的一种。

输出格式

对于每个 empty 和 query 操作都要输出一个查询结果,每个结果占一行。

其中,empty 操作的查询结果为 YES 或 NOquery 操作的查询结果为一个整数,表示队头元素的值。

数据范围

1≤M≤1000001≤M≤100000,
1≤x≤1091≤x≤109,
所有操作保证合法。

输入样例:

10
push 6
empty
query
pop
empty
push 3
push 4
pop
query
push 6

输出样例:

NO
6
YES
4

#include<iostream>
#include<queue>
using namespace std;
queue<int>A;
int n,x;
string nstr;
int main(){scanf("%d",&n);while(n--){cin>>nstr;if(nstr=="push"){scanf("%d",&x);A.push(x);}else if(nstr=="pop"){A.pop();}else if(nstr=="empty"){puts(A.empty()?"YES":"NO");}else printf("%d\n",A.front());}return 0;
}

单调栈

给定一个长度为 NN 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1−1。

输入格式

第一行包含整数 NN,表示数列长度。

第二行包含 NN 个整数,表示整数数列。

输出格式

共一行,包含 NN 个整数,其中第 ii 个数表示第 ii 个数的左边第一个比它小的数,如果不存在则输出 −1−1。

数据范围

1≤N≤1051≤N≤105
1≤数列中元素≤1091≤数列中元素≤109

输入样例:

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2
//cpp
//参考y总
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;int main()
{int n;cin >> n;while (n -- ){int x;scanf("%d", &x);while (tt && stk[tt] >= x) tt -- ;//如果栈顶元素大于当前待入栈元素,则出栈if (!tt) printf("-1 ");//如果栈空,则没有比该元素小的值。else printf("%d ", stk[tt]);//栈顶元素就是左侧第一个比它小的元素。stk[ ++ tt] = x;}return 0;
}作者:Hasity
链接:https://www.acwing.com/solution/content/27437/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

单调队列

给定一个大小为 n≤106n≤106 的数组。

有一个大小为 kk 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 kk 个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],kk 为 33。

窗口位置 最小值 最大值
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式

输入包含两行。

第一行包含两个整数 nn 和 kk,分别代表数组长度和滑动窗口的长度。

第二行有 nn 个整数,代表数组的具体数值。

同行数据之间用空格隔开。

输出格式

输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:

8 3
1 3 -1 -3 5 3 6 7

输出样例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7
# include <iostream>
using namespace std;
const int N = 1000010;
int a[N], q[N], hh, tt = -1;int main()
{int n, k;cin >> n >> k;for (int i = 0; i < n; ++ i){scanf("%d", &a[i]);if (i - k + 1 > q[hh]) ++ hh;                  // 若队首出窗口,hh加1while (hh <= tt && a[i] <= a[q[tt]]) -- tt;    // 若队尾不单调,tt减1q[++ tt] = i;                                  // 下标加到队尾if (i + 1 >= k) printf("%d ", a[q[hh]]);       // 输出结果}cout << endl;hh = 0; tt = -1;                                   // 重置!for (int i = 0; i < n; ++ i){if (i - k + 1 > q[hh]) ++ hh;while (hh <= tt && a[i] >= a[q[tt]]) -- tt;q[++ tt] = i;if (i + 1 >= k) printf("%d ", a[q[hh]]);}return 0;
}作者:Alicia编程果果
链接:https://www.acwing.com/solution/content/6564/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

KMP

给定一个模式串 SS,以及一个模板串 PP,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模板串 PP 在模式串 SS 中多次作为子串出现。

求出模板串 PP 在模式串 SS 中所有出现的位置的起始下标。

输入格式

第一行输入整数 NN,表示字符串 PP 的长度。

第二行输入字符串 PP。

第三行输入整数 MM,表示字符串 SS 的长度。

第四行输入字符串 SS。

输出格式

共一行,输出所有出现位置的起始下标(下标从 00 开始计数),整数之间用空格隔开。

数据范围

1≤N≤1051≤N≤105
1≤M≤1061≤M≤106

输入样例:

3
aba
5
ababa

输出样例:

0 2
#include<iostream>using namespace std;const int N=100010,M=1000010;char q[N],s[M];int ne[N];//保存next数组int main()
{int n,m;cin>>n>>q+1>>m>>s+1;//下标均从1开始for(int i=2,j=0;i<=n;i++)//j表示匹配成功的长度,i表示q数组中的下标,因为q数组的下标是从1开始的,只有1个时,一定为0,所以i从2开始{while(j&&q[i]!=q[j+1]) j=ne[j];//如果不行可以换到next数组if(q[i]==q[j+1]) j++;//成功了就加1ne[i]=j;//对应其下标}//j表示匹配成功的长度,因为刚开始还未开始匹配,所以长度为0for(int i=1,j=0;i<=m;i++){while(j&&s[i]!=q[j+1]) j=ne[j];//如果匹配不成功,则换到j对应的next数组中的值if(s[i]==q[j+1]) j++;//匹配成功了,那么j就加1,继续后面的匹配if(j==n)//如果长度等于n了,说明已经完全匹配上去了{printf("%d ",i-j);//因为题目中的下标从0开始,所以i-j不用+1;j=ne[j];//为了观察其后续是否还能跟S数组后面的数配对成功}}return 0;
}

Trie

维护一个字符串集合,支持两种操作:

  1. I x 向集合中插入一个字符串 xx;
  2. Q x 询问一个字符串在集合中出现了多少次。

共有 NN 个操作,输入的字符串总长度不超过 105105,字符串仅包含小写英文字母。

输入格式

第一行包含整数 NN,表示操作数。

接下来 NN 行,每行包含一个操作指令,指令为 I x 或 Q x 中的一种。

输出格式

对于每个询问指令 Q x,都要输出一个整数作为结果,表示 xx 在集合中出现的次数。

每个结果占一行。

数据范围

1≤N≤2∗1041≤N≤2∗104

输入样例:

5
I abc
Q abc
Q ab
I ab
Q ab

输出样例:

1
0
1

v//Trie树快速存储字符集合和快速查询字符集合
#include <iostream>using namespace std;const int N = 100010;
//son[][]存储子节点的位置,分支最多26条;
//cnt[]存储以某节点结尾的字符串个数(同时也起标记作用)
//idx表示当前要插入的节点是第几个,每创建一个节点值+1
int son[N][26], cnt[N], 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];  //使“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 m;cin >> m;while(m--){char op[2];scanf("%s%s", op, str);if(*op == 'I') insert(str);else printf("%d\n", query(str));}return 0;
}作者:四谷夕雨
链接:https://www.acwing.com/solution/content/14695/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在给定的 NN 个整数 A1,A2……ANA1,A2……AN 中选出两个进行 xorxor(异或)运算,得到的结果最大是多少?

输入格式

第一行输入一个整数 NN。

第二行输入 NN 个整数 A1A1~ANAN。

输出格式

输出一个整数表示答案。

数据范围

1≤N≤1051≤N≤105,
0≤Ai<2310≤Ai<231

输入样例:

3
1 2 3

输出样例:

3

道题的启示是:字典树不单单可以高效存储和查找字符串集合,还可以存储二进制数字
思路:将每个数以二进制方式存入字典树,找的时候从最高位去找有无该位的异.cin.tie(0)加速读入

#include<iostream>
#include<algorithm>
using namespace std;
int const N=100010,M=31*N;int n;
int a[N];
int son[M][2],idx;
//M代表一个数字串二进制可以到多长void insert(int x)
{int p=0;  //根节点for(int i=30;i>=0;i--){int u=x>>i&1;   /取X的第i位的二进制数是什么  x>>k&1(前面的模板)if(!son[p][u]) son[p][u]=++idx; ///如果插入中发现没有该子节点,开出这条路p=son[p][u]; //指针指向下一层}
}
int search(int x)
{int p=0;int res=0;for(int i=30;i>=0;i--){                               ///从最大位开始找int u=x>>i&1;if(son[p][!u]) 如果当前层有对应的不相同的数{   ///p指针就指到不同数的地址p=son[p][!u];res=res*2+1;///*2相当左移一位  然后如果找到对应位上不同的数res+1 例如    001}                                                       ///       010 else                                                      --->011                                                                           //刚开始找0的时候是一样的所以+0    到了0和1的时候原来0右移一位,判断当前位是同还是异,同+0,异+1{p=son[p][u];res=res*2+0;}}return res;
}
int main(void)
{cin.tie(0);cin>>n;idx=0;for(int i=0;i<n;i++){cin>>a[i];insert(a[i]);}int res=0;for(int i=0;i<n;i++){   res=max(res,search(a[i]));  ///search(a[i])查找的是a[i]值的最大与或值}cout<<res<<endl;
}作者:小菜鸡UP
链接:https://www.acwing.com/solution/content/9587/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

并查集

一共有 nn 个数,编号是 1∼n1∼n,最开始每个数各自在一个集合中。

现在要进行 mm 个操作,操作共有两种:

  1. M a b,将编号为 aa 和 bb 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 aa 和 bb 的两个数是否在同一个集合中;

输入格式

第一行输入整数 nn 和 mm。

接下来 mm 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 aa 和 bb 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes

开始时每个集合都是一个独立的集合,并且都是等于自己本身下标的数
例如:
p[5]=5,p[3]=3;
如果是M操作的话那么就将集合进行合并,合并的操作是:
p[3]=p[5]=5;
所以3的祖宗节点便成为了5
此时以5为祖宗节点的集合为{5,3}
如果要将p[9]=9插入到p[3]当中,应该找到3的祖宗节点,
然后再把p[9]=9插入其中,所以p[9]=find(3);(find()函数用于查找祖宗节点)
也可以是p[find(9)]=find(3),因为9的节点本身就是9
此时以5为祖宗节点的集合为{5,3,9};
如果碰到多个数的集合插入另一个集合当中其原理是相同的
例如:
上述中以5为祖宗节点的是p[5],p[3],p[9];(即p[5]=5,p[3]=5,p[9]=5)
再构造一个以6为祖宗节点的集合为{6,4,7,10}
如果要将以6为祖宗节点的集合插入到以5为祖宗节点的集合,则该操作可以是
p[6]=find(3)(或者find(9),find(5))
此时p[6]=5
当然如果是以6为祖宗节点集合中的4,7,10则可以这样
p[find(4)]=find(3)
或者p[find(7)]=find(3)均可以
此时以6为祖宗节点的集合的祖宗节点都成为了5

作者:E.lena
链接:https://www.acwing.com/solution/content/20690/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

#include<iostream>using namespace std;const int N=100010;
int p[N];//定义多个集合int find(int x)
{if(p[x]!=x) p[x]=find(p[x]);/*经上述可以发现,每个集合中只有祖宗节点的p[x]值等于他自己,即:p[x]=x;*/return p[x];//找到了便返回祖宗节点的值
}int main()
{int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) p[i]=i;while(m--){char op[2];int a,b;scanf("%s%d%d",op,&a,&b);if(*op=='M') p[find(a)]=find(b);//集合合并操作elseif(find(a)==find(b))//如果祖宗节点一样,就输出yesprintf("Yes\n");elseprintf("No\n");}return 0;
}作者:E.lena
链接:https://www.acwing.com/solution/content/20690/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

给定一个包含 nn 个点(编号为 1∼n1∼n)的无向图,初始时图中没有边。

现在要进行 mm 个操作,操作共有三种:

  1. C a b,在点 aa 和点 bb 之间连一条边,aa 和 bb 可能相等;
  2. Q1 a b,询问点 aa 和点 bb 是否在同一个连通块中,aa 和 bb 可能相等;
  3. Q2 a,询问点 aa 所在连通块中点的数量;

输入格式

第一行输入整数 nn 和 mm。

接下来 mm 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 aa 和 bb 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 aa 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

维护连通块size的并查集
一、初始化
void init() {
    for (int i=1; i<=n; i++) {
        fa[i] = i;
        size[i] = 1;
    }
}
二、找祖源
int find(int x) {
    if(fa[x]==x) return x;
    else return fa[x] = find(fa[x]);
}
三、合并连通块
void merge(int a,int b) {
    int x = find(a);
    int y = find(b);
    fa[x] = y;
    size[y] += size[x];
}
四、询问是否连通
bool ask(int a,int b) {
    return find(a)==find(b);
}
特别注意:
size只有祖节点的有意义
要特别注意所有处理size的地方,都要“归根结底”

作者:Shadow
链接:https://www.acwing.com/solution/content/5707/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

下面的代码编译错误

#include<bits/stdc++.h>
#define read(x) scanf("%d",&x)
using namespace std;
const int N = 1e5+5;
int n,m,a,b,fa[N], size[N];
string act;void init() {for (int i=1; i<=n; i++) {fa[i] = i;size[i] = 1;}
}int find(int x) {if(fa[x]==x) return x;else return fa[x] = find(fa[x]);
}void merge(int a,int b) {int x = find(a);int y = find(b);fa[x] = y;size[y] += size[x];
}bool ask(int a,int b) {return find(a)==find(b);
}int main() {read(n),read(m);init();while(m--) {cin>>act;if(act=="C") {read(a),read(b);if(!ask(a,b)) merge(a,b);} else if(act=="Q1") {read(a),read(b);ask(a,b) ? printf("Yes\n") : printf("No\n");} else {read(a);printf("%d\n",size[find(a)]);}}   return 0;
}作者:Shadow
链接:https://www.acwing.com/solution/content/5707/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
/** Project: 09_Disjoint_Set* File Created:Friday, January 8th 2021, 2:55:49 pm* Author: Bug-Free* Problem: AcWing 837. 连通块中点的数量*/#include <iostream>using namespace std;const int N = 1e5 + 10;int n, m;
int fa[N], cnt[N];int Get(int x) {if (x == fa[x]) {return x;}return fa[x] = Get(fa[x]);
}int main() {cin >> n >> m;for (int i = 1; i <= n; i++) {fa[i] = i;cnt[i] = 1;}string op;int a, b;while (m--) {cin >> op;if (op == "C") {cin >> a >> b;// if (Get(a) != Get(b)) {//     int t= Get(a);//     fa[Get(a)] = Get(b);//     cnt[Get(b)] += cnt[t];// }a = Get(a), b = Get(b);if (a != b) {fa[a] = b;cnt[b] += cnt[a];}} else if (op == "Q1") {cin >> a >> b;cout << (Get(a) == Get(b) ? "Yes" : "No") << endl;} else {cin >> a;cout << cnt[Get(a)] << endl;}}return 0;
}作者:Bug-Free
链接:https://www.acwing.com/solution/content/28470/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

动物王国中有三类动物 A,B,CA,B,C,这三类动物的食物链构成了有趣的环形。

AA 吃 BB,BB 吃 CC,CC 吃 AA。

现有 NN 个动物,以 1∼N1∼N 编号。

每个动物都是 A,B,CA,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 NN 个动物所构成的食物链关系进行描述:

第一种说法是 1 X Y,表示 XX 和 YY 是同类。

第二种说法是 2 X Y,表示 XX 吃 YY。

此人对 NN 个动物,用上述两种说法,一句接一句地说出 KK 句话,这 KK 句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  1. 当前的话与前面的某些真的话冲突,就是假话;
  2. 当前的话中 XX 或 YY 比 NN 大,就是假话;
  3. 当前的话表示 XX 吃 XX,就是假话。

你的任务是根据给定的 NN 和 KK 句话,输出假话的总数。

输入格式

第一行是两个整数 NN 和 KK,以一个空格分隔。

以下 KK 行每行是三个正整数 D,X,YD,X,Y,两数之间用一个空格隔开,其中 DD 表示说法的种类。

若 D=1D=1,则表示 XX 和 YY 是同类。

若 D=2D=2,则表示 XX 吃 YY。

输出格式

只有一个整数,表示假话的数目。

数据范围

1≤N≤500001≤N≤50000,
0≤K≤1000000≤K≤100000

输入样例:

100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5

输出样例:

3

另一个题解写了拆点并查集的做法,我这里再写一下带权并查集的做法

本题的关系有三层 -> a -> b -> c -> ,但不同的是本题的关系是有向的,也就是说a和b如果是敌对关系,那么b和a就不是敌对关系。

关系传递的本质实际上是向量的运算。
还是设 d[x] 表示 x 与 fa[x] 的关系,0 代表是同类,1 代表是x吃fa[x], 根据关系图自然2就代表x被fa[x]吃。

下面假设a的祖先是x,b的祖先是y,为简化书写,设他们的向量关系为

a⃗ =(a,x)b⃗ =(b,y)a→=(a,x)b→=(b,y)
给出的关系设为rel=ab→rel=ab→
以下的向量关系均用以上符号代替,实际运算时自行带入二元组运算即可

若x=yx=y
想要知道 ab→ab→ ,则需要 a⃗ −b⃗ a→−b→ 然后对3取模并移动到正整数
此时得到的关系0代表ab是同类,1代表a吃b,2代表a被b吃。直接与rel进行比较即可。

如果x和y不等,那么这个给出的关系肯定是合法的
合并的时候同样fa[x] = y,x⃗ =b⃗ +ab→−a⃗ x→=b→+ab→−a→

然后就可以愉快的搞了

作者:这个显卡不太冷
链接:https://www.acwing.com/solution/content/1357/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 233;
int fa[maxn], d[maxn];
int ff(int x)
{if(fa[x] == x) return x;int r = ff(fa[x]);d[x] += d[fa[x]];return fa[x] = r;
}
int main()
{int n,k; cin >> n >> k;for(int i = 0; i <= n; i++) fa[i] = i;int ans = 0;for(int i = 1; i <= k; i++){int t, a, b;scanf("%d%d%d", &t, &a, &b);if(a > n || b > n) {ans ++; continue;}else if(t == 2 && a == b) {ans++; continue;}else{int rel;if(t == 2) rel = 1;else rel = 0;int x = ff(a), y = ff(b);if(x == y) {if((((d[a] - d[b]) % 3) + 3) % 3 != rel)ans++;}else{fa[x] = y;d[x] = d[b] - (d[a] - rel);}}}cout<< ans;
}作者:这个显卡不太冷
链接:https://www.acwing.com/solution/content/1357/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

堆排序

输入一个长度为 nn 的整数数列,从小到大输出前 mm 小的数。

输入格式

第一行包含整数 nn 和 mm。

第二行包含 nn 个整数,表示整数数列。

输出格式

共一行,包含 mm 个整数,表示整数数列中前 mm 小的数。

数据范围

1≤m≤n≤1051≤m≤n≤105,
1≤数列中元素≤1091≤数列中元素≤109

输入样例:

5 3
4 5 1 3 2

输出样例:

1 2 3

#include<iostream>
#include<algorithm>using namespace std;const int N = 100010;int h[N], mySize;int n, m;void down(int u)
{int t = u;if (2 * u <= mySize && h[t] > h[2 * u])t = 2 * u;if (2 * u + 1 <= mySize && h[t] > h[2 * u + 1])t = 2 * u + 1;if (u != t){swap(h[u], h[t]);down(t);}
}int main()
{cin >> n >> m;mySize = n;for (int i = 1; i <= n; i++)scanf("%d", &h[i]);for (int i = n / 2; i; i--)down(i);while (m--){cout << h[1] << " ";h[1] = h[mySize--];down(1);}return 0;
}

模拟堆

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数 xx;
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第 kk 个插入的数;
  5. C k x,修改第 kk 个插入的数,将其变为 xx;

现在要进行 NN 次操作,对于所有第 22 个操作,输出当前集合的最小值。

输入格式

第一行包含整数 NN。

接下来 NN 行,每行包含一个操作指令,操作指令为 I xPMDMD k 或 C k x 中的一种。

输出格式

对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。

每个结果占一行。

数据范围

1≤N≤1051≤N≤105
−109≤x≤109−109≤x≤109
数据保证合法。

输入样例:

8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

-10
6

问题
如何理解AcWing 模拟堆这道题中的heap_swap,hp[N], ph[N]?

详解
重点:题目中第k个插入,这里的k相当于链表中的idx,是节点的唯一标识
不理解idx到底是啥意思的可以先看看这篇,其中总结了对链表,Trie树,堆中idx的理解:https://www.acwing.com/solution/content/5673/

1. 关于idx
堆中的每次插入都是在堆尾,但是堆中经常有up和down操作。所以节点与节点的关系并不是用一个ne[idx][2]可以很好地维护的。但是好在堆是个完全二叉树。子父节点的关系可以通过下标来联系(左儿子2n,右儿子2n+1)。就数组模拟来说,知道数组的下标就知道结点在堆中的位置。所以核心就在于即使有down和up操作也能维护堆数组的下标(k)和结点(idx)的映射关系。 比如说:h[k] = x, h数组存的是节点的值,按理来说应该h[idx]来存,但是节点位置总是在变的,因此维护k和idx的映射关系就好啦

举例: 用ph数组来表示ph[idx] = k(idx到下标), 那么结点值为h[ph[idx]], 儿子为ph[idx] * 2和ph[idx] * 2 + 1, 这样值和儿子结点不就可以通过idx联系在一起了吗?

2. 理解hp与ph数组
从上面讨论的可以知道,ph数组主要用于帮助从idx映射到下标k,似乎有了ph数组就可以完成所有操作了,但为什么还要有一个hp数组呢?
原因就在于在swap操作中我们输入是堆数组的下标,无法知道每个堆数组的k下标对应idx(第idx个插入),所以需要hp数组方便查找idx。

void heap_swap(int a, int b)
{
    swap(ph[hp[a]], ph[hp[b]]); 
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}
3. 举例:堆中的插入操作
注意: 在堆这个数据结构中,数据的插入都是插入到堆尾,然后再up

if (op == "I")
{
    scanf("%d", &x);
    size ++ ;
    idx ++ ; //记录第几次插入(设置新的idx)
    ph[idx] = size, hp[size] = idx; //每次插入都是在堆尾插入(设置ph与hp)
    h[ph[idx]] = x; //记录插入的值 
    up(ph[idx]);
}
4. 举例:删除第idx个插入元素
删除操作,三个步骤:
1. 找到第idx个插入元素在堆数组中的位置(堆数组下标)
2. 与堆尾元素交换
3. 在原来第idx个元素所在的位置进行down和up操作。(up,down,swap操作的都输入都是下标)

很显然,在第一步中,显然ph[idx]查找即可。第二步,直接swap操作。第三步需要找到原来第idx的元素所在的位置,由于交换完后ph[idx]的值变了,变为堆尾的下标了,所以必须要在之前保存ph[idx]的值

if (op == "D")
{
    scanf("%d", &idx);
    k = ph[idx]; //必须要保存当前被删除结点的下标
    heap_swap(k, size);//第idx个插入的元素移到了堆尾,此时ph[idx]指向堆尾 
    size --;  //删除堆尾
    up(k);//k是之前记录被删除的结点的下标
    down(k);
}

作者:Darron
链接:https://www.acwing.com/solution/content/5661/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

#include<iostream>
#include<algorithm>
using namespace std;const int N=1e5+10;
int h[N];   //堆
int ph[N];  //存放第k个插入点的下标
int hp[N];  //存放堆中点的插入次序
int cur_size;   //size 记录的是堆当前的数据多少//这个交换过程其实有那么些绕 但关键是理解 如果hp[u]=k 则ph[k]=u 的映射关系
//之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
//从而我们需要对应到原先第K个堆中元素
//如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换
//h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
void heap_swap(int u,int v)
{   swap(h[u],h[v]); swap(hp[u],hp[v]);     swap(ph[hp[u]],ph[hp[v]]);            }void down(int u)
{int t=u;if(u*2<=cur_size&&h[t]>h[u*2]) t=u*2;if(u*2+1<=cur_size&&h[t]>h[u*2+1])  t=u*2+1;if(u!=t){heap_swap(u,t);down(t);}
}
void up(int u)
{if(u/2>0&&h[u]<h[u/2]) {heap_swap(u,u/2);up(u>>1);}
}int main()
{int n;cin>>n;int m=0;      //m用来记录插入的数的个数//注意m的意义与cur_size是不同的 cur_size是记录堆中当前数据的多少//对应上文 m即是hp中应该存的值while(n--){string op;int k,x;cin>>op;if(op=="I"){cin>>x;m++;h[++cur_size]=x;ph[m]=cur_size;hp[cur_size]=m;//down(size);up(cur_size);}else if(op=="PM")    cout<<h[1]<<endl;else if(op=="DM"){heap_swap(1,cur_size);cur_size--;down(1);}else if(op=="D"){cin>>k;int u=ph[k];                //这里一定要用u=ph[k]保存第k个插入点的下标heap_swap(u,cur_size);          //因为在此处heap_swap操作后ph[k]的值已经发生 cur_size--;                    //如果在up,down操作中仍然使用ph[k]作为参数就会发生错误up(u);down(u);}else if(op=="C"){cin>>k>>x;h[ph[k]]=x;                 //此处由于未涉及heap_swap操作且下面的up、down操作只会发生一个所以down(ph[k]);                //所以可直接传入ph[k]作为参数up(ph[k]);}}return 0;
}作者:Ni
链接:https://www.acwing.com/solution/content/5541/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

哈希表

维护一个集合,支持如下几种操作:

  1. I x,插入一个数 xx;
  2. Q x,询问数 xx 是否在集合中出现过;

现在要进行 NN 次操作,对于每个询问操作输出对应的结果。

输入格式

第一行包含整数 NN,表示操作数量。

接下来 NN 行,每行包含一个操作指令,操作指令为 I xQ x 中的一种。

输出格式

对于每个询问指令 Q x,输出一个询问结果,如果 xx 在集合中出现过,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤N≤1051≤N≤105
−109≤x≤109−109≤x≤109

输入样例:

5
I 1
I 2
I 3
Q 2
Q 5

输出样例:

Yes
No
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int const maxn=100003;int h[maxn],e[maxn],ne[maxn],idx;
h[]是哈希函数的一维数组//e[]是链表中存的值//ne[]是指针存的指向的地址//idx是当前指针
void insert(int x)
{int k=(x%maxn + maxn)%maxn; 对负数的处理,k是哈希值e[idx]=x;ne[idx]=h[k];h[k]=idx++;    //如果不同单链表的idx都是从0开始单独计数,//那么不同链表之间可能会产生冲突。//这里的模型是这样的:e[]和ne[]相当于一个大池子,里面是单链表中的节点,会被所有单点表共用,idx相当于挨个分配池子中的节点的指针。//比如如果第0个节点被分配给了第一个单链表,那么所有单链表就只能从下一个节点开始分配,所以所有单链表需要共用一个idx。
}
bool find(int x)
{int k= (x%maxn+ maxn) %maxn;  ///为了让负数在整数有映射,负数的取模还是负数,加上maxn后为正,再%即可for(int i=h[k];i!=-1;i=ne[i])if(e[i]==x)return true;return false;
}
int main(void)
{cin.tie(0);int n;cin>>n;memset(h,-1,sizeof(h));  ///所有槽都清空,对应的是单链表的头(head)[注:head存的是地址,详见单链表的课]指针为-1while(n--){char op[2];int x;scanf("%s%d", op, &x);if(op[0]=='I') insert(x);else {if(find(x)) cout<<"Yes"<<endl;else cout<<"No"<<endl;}}
return 0;
}作者:小菜鸡UP
链接:https://www.acwing.com/solution/content/9568/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

给定一个长度为 nn 的字符串,再给定 mm 个询问,每个询问包含四个整数 l1,r1,l2,r2l1,r1,l2,r2,请你判断 [l1,r1][l1,r1] 和 [l2,r2][l2,r2] 这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式

第一行包含整数 nn 和 mm,表示字符串长度和询问次数。

第二行包含一个长度为 nn 的字符串,字符串中只包含大小写英文字母和数字。

接下来 mm 行,每行包含四个整数 l1,r1,l2,r2l1,r1,l2,r2,表示一次询问所涉及的两个区间。

注意,字符串的位置从 11 开始编号。

输出格式

对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤1051≤n,m≤105

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

算法1
(字符串哈希) O(n)+O(m)O(n)+O(m)
全称字符串前缀哈希法,把字符串变成一个p进制数字(哈希值),实现不同的字符串映射到不同的数字。
对形如 X1X2X3⋯Xn−1XnX1X2X3⋯Xn−1Xn 的字符串,采用字符的ascii 码乘上 P 的次方来计算哈希值。

映射公式 (X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0)modQ(X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0)modQ
注意点:
1. 任意字符不可以映射成0,否则会出现不同的字符串都映射成0的情况,比如A,AA,AAA皆为0
2. 冲突问题:通过巧妙设置P (131 或 13331) , Q (264)(264)的值,一般可以理解为不产生冲突。

问题是比较不同区间的子串是否相同,就转化为对应的哈希值是否相同。
求一个字符串的哈希值就相当于求前缀和,求一个字符串的子串哈希值就相当于求部分和。

前缀和公式 h[i+1]=h[i]×P+s[i]h[i+1]=h[i]×P+s[i] i∈[0,n−1]i∈[0,n−1] h为前缀和数组,s为字符串数组
区间和公式 h[l,r]=h[r]−h[l−1]×Pr−l+1h[l,r]=h[r]−h[l−1]×Pr−l+1
区间和公式的理解: ABCDE 与 ABC 的前三个字符值是一样,只差两位,
乘上 P2P2 把 ABC 变为 ABC00,再用 ABCDE - ABC00 得到 DE 的哈希值。

作者:chocolate-emperor
链接:https://www.acwing.com/solution/content/24738/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

#include<iostream>
#include<cstdio>
#include<string>
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;
}作者:chocolate-emperor
链接:https://www.acwing.com/solution/content/24738/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

数据结构模板题(必备)Day2相关推荐

  1. 搜索与图论模板题(必备)Day3

    DFS 给定一个整数 nn,将数字 1∼n1∼n 排成一排,将会有很多种排列方法. 现在,请你按照字典序将所有的排列方法输出. 输入格式 共一行,包含一个整数 nn. 输出格式 按字典序输出所有排列方 ...

  2. ~~字符串哈希(数据结构)(附模板题AcWing 841 字符串哈希)

    核心思想: 将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低. 小技巧: 取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果. ...

  3. ~~一般哈希(数据结构)(附模板题AcWing 840 模拟散列表)

    (1) 拉链法 int h[N], e[N], ne[N], idx;// 向哈希表中插入一个数 void insert(int x) {int k = (x % N + N) % N;e[idx] ...

  4. ~~堆(数据结构)(附模板题 AcWing 838. 堆排序)

    模板 // h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1 // ph[k]存储第k个插入的点在堆中的位置 // hp[k]存储堆中下标是k的点是第几个插入的 int ...

  5. ~~队列(数据结构)(附模板题 AcWing 829. 模拟队列)

    1. 普通队列: // hh 表示队头,tt表示队尾 int q[N], hh = 0, tt = -1;// 向队尾插入一个数 q[ ++ tt] = x;// 从队头弹出一个数 hh ++ ;// ...

  6. LeetCode 牛客网 C++ 算法 刷题必备基础知识

    LeetCode 牛客网 C++ 算法 刷题必备基础知识 {ignore} 文章目录 LeetCode 牛客网 C++ 算法 刷题必备基础知识 {ignore} main综合模板 数组排序库 字符串库 ...

  7. 一起开心2020暑假训练第二周 图论(模板题)

    比赛链接: 文章目录 A HDU 1285 一 B HDU 1863 起 C POJ 2387 开 D POJ 1502 心 E HDU 5922 图 F HDU 2112 论 A HDU 1285 ...

  8. BZOJ 3223: Tyvj 1729 文艺平衡树-Splay树(区间翻转)模板题

    3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 6881  Solved: 4213 [Submit][S ...

  9. 【BZOJ - 3224】普通平衡树(Splay模板题)

    题干: 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 1. 插入x数 2. 删除x数(若有多个相同的数,因只删除一个) 3. 查询x数的排名(若有多个相同的数,因输出最 ...

最新文章

  1. JAVA实现汉字转换为拼音 pinyin4j/JPinyin
  2. 线程WAITING--BlockingQueueDemo
  3. java8 di_java8 多个list对象用lambda求差集操作
  4. EXE.DLL文件图标导出器[免费下载]
  5. SQL Server中的文件流
  6. linux 学习笔记 -vim 操作
  7. Ruby数据结构-数组和哈希表
  8. 论文笔记_S2D.58_2018-ICRA-基于直接法的使用LiDAR稀疏深度的视觉-激光雷达SLAM
  9. [C++]踩坑日记:内存别名
  10. 在线 服务器 web,web服务器是什么?
  11. python gui 之库tkinter笔记
  12. 使用netstat命令统计established状态的连接数
  13. 走进游戏中的美术:游戏美术风格介绍
  14. 乐高教育版45544零件---分类识别
  15. CCF大会腾源会专场即将召开,聚焦基础软件与开发语言未来发展
  16. 2021-10-27 - 开发人员将大多数时间花到了探究系统本身上
  17. C语言实现高精度减法
  18. 参考文献起止页码怎么写_参考文献书写格式
  19. 平面极坐标系下质点的运动方程
  20. 数据挖掘:概念与技术(第三版)之第十章的学习记录

热门文章

  1. unity sprite保存成PNG图片
  2. debian 编译linux内核源码,安装debian总结以及编译linux内核
  3. 推荐系统从零单排系列(四)—Word2Vec理论与实践(上)
  4. 数学建模论文写作方法之一(附录)
  5. 程序员锁死服务器搞垮上家公司后,下家公司说不敢惹不敢惹!
  6. html中查看必应地图,必应地图显示点击事件经纬度
  7. js websocket断开重连机制
  8. php 出现Warning A non-numeric value encountered问题的原因及解决方法
  9. 华为数通笔记VXLANBGP EVPN
  10. 开源版的高仿 “ 微信 ”,吊炸天!