1、模拟堆
实际上与昨天的堆排序一样的操作,不过多了几个别的操作,具体看下图,主要还是昨天那个经常考所以这个看看就好:
维护一个集合,初始时集合为空,支持如下几种操作:
- “I x”,插入一个数x;
- “PM”,输出当前集合中的最小值;
- “DM”,删除当前集合中的最小值(数据保证此时的最小值唯一);
- “D k”,删除第k个插入的数;
- “C k x”,修改第k个插入的数,将其变为x;
现在要进行N次操作,对于所有第2个操作,输出当前集合的最小值。
输入格式
第一行包含整数N。
接下来N行,每行包含一个操作指令,操作指令为”I x”,”PM”,”DM”,”D 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
#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 记录的是堆当前的数据多少
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;
}
2、哈希表
从0~10^9映射到0~10^5
拉链法:一般只有查找和添加操作
维护一个集合,支持如下几种操作:
- “I x”,插入一个数x;
- “Q x”,询问数x是否在集合中出现过;
现在要进行N次操作,对于每个询问操作输出对应的结果。
输入格式
第一行包含整数N,表示操作数量。
接下来N行,每行包含一个操作指令,操作指令为”I x”,”Q x”中的一种。
输出格式
对于每个询问指令“Q x”,输出一个询问结果,如果x在集合中出现过,则输出“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
//步骤:哈希表的拉链法得定义h[N]作为链表头节点,每个节点下面挂着一条链表,所以得定义链表基本的e[N],ne[N],idx
//插入操作:先把数据mod N,压缩到N以内,然后链表的插入操作:存x,ne[idx]指向第k个头节点h[k],h[k] ++;
//查找操作:先把数据mod N,压缩到N以内,然后链表的查询操作:for循环,找到一个e[i] == x的点return true;否则return false
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100003;输入的数必须是质数,100000之后的第一个质数是100003
int h[N], e[N], ne[N], idx;//h是槽,加的链是链表包括e, ne, idx
void insert(int x){
int k = (x % N + N) % N;防止x % N < 0,保证k是正数
//链表插入操作
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++;
}
//链表查询
bool find(int x){
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i])
if (e[i] == x) return true;
return false;
}
int main()
{
int n;
cin >> n;
memset(h, -1, sizeof h);
while (n --){
char op[2];
int x;
cin >> op;
cin >> x;
if (op[0] == 'I') insert(x);
else {
if (find(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
3、字符串哈希
预处理所有前缀的哈希转换成p进制,mod上q,把任何一个字符串映射到 从0~Q - 1的数。
注意:1、不能映射成0。 2、理想状态不存在冲突。
利用前面求得的前缀,用公式来求哈希,求L到R区间的哈希值:(重要)
给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数l1,r1,l2,r2l1,r1,l2,r2,请你判断[l1,r1l1,r1]和[l2,r2l2,r2]这两个区间所包含的字符串子串是否完全相同。
字符串中只包含大小写英文字母和数字。
输入格式
第一行包含整数n和m,表示字符串长度和询问次数。
第二行包含一个长度为n的字符串,字符串中只包含大小写英文字母和数字。
接下来m行,每行包含四个整数l1,r1,l2,r2l1,r1,l2,r2,表示一次询问所涉及的两个区间。
注意,字符串的位置从1开始编号。
输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出“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
//步骤:数据范围比较大所以用ULL来表示该类型变量,P是131包括字母和数字总和大小
//公式:求L到R区间的哈希值:h[r] - h[l - 1] * p[r - l + 1];
#include <iostream>
using namespace std;
typedef unsigned long long ULL;//用ULL来表示该类型变量
const int N = 100010, P = 131;
int n, m;
char str[N];
ULL h[N], p[N];//h[N]存哈希值,p[N]存P的多少次方
ULL get(int l, int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
cin >> n >> m >> str + 1;
p[0] = 1;
//初始化
for (int i = 1; i <= n; i ++){
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i];
}
while (m --){
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2)) puts("Yes");
else puts("No");
}
return 0;
}
4、DFS-排列数字
注意:恢复现场
给定一个整数n,将数字1~n排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤71≤n≤7
输入样例:
3
输出样例:
//dfs步骤:
//1、当走到第n个位置,说明我们把所有位置填满了
//2、否则枚举当前位置能填哪些数,如果该数没被用过将i放到当前位置并标记,递归到下一层,最好恢复现场。
#include <iostream>
using namespace std;
const int N = 10;//数范围1-7
int n;
int path[N];//存状态
bool st[N];//当前位置需要填哪些数,需要开bool数组,如果该点为true则表示该点被用过了
void dfs(int u){
if (u == n){
//输出结果
for (int i = 0; i < n; i ++) cout << path[i] << ' ';
puts("");
return;
}
for (int i = 1; i <= n; i ++){//枚举当前位置能填哪些数
if (!st[i]){
path[u] = i;//将i放到当前位置
st[i] = true;//记录i已被用过
dfs(u + 1);//递归到下一层
//当dfs结束之后,一条遍历完所以需要恢复现场
st[i] = false;
}
}
}
int main()
{
cin >> n;
dfs(0);
return 0;
}