#include
#include
using namespace std;
const int N=1e5+10;
int head,e[N],ne[N],idx;
void init(){
head=-1;
idx=0;
}
void add_to_head(int x){//头节点插入
e[idx]=x;
ne[idx]=head;
head=idx++;
}
void remove(int k){//删除
ne[k]=ne[ne[k]];
}
void add(int k,int x){//插入
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx++;
}
int main(){
init();
int m;
cin>>m;
while (m--){
char c;
cin>>c;
int k,x;
if (c=='H'){
cin>>x;
add_to_head(x);
}else if (c=='D'){
cin>>k;
if (!k){
head=ne[head];
}
remove(k-1);
}else{
cin>>k>>x;
add(k-1,x);
}
}
for (int i=head;i!=-1;i=ne[i]){
cout<
}
return 0;
}
#include
#include
#include
using namespace std;
const int N=1e5+5;
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++;
}
void remove(int k){//删除
l[r[k]]=l[k];
r[l[k]]=r[k];
}
int main(){
init();
int m;
scanf("%d",&m);
while (m--){
string s;
cin>>s;
int k,x;
if (s=="L"){
scanf("%d",&x);
add(0,x);
}else if (s=="R"){
scanf("%d",&x);
add(l[1],x);
}else if (s=="D"){
scanf("%d",&k);
remove(k+1);//注意
}else if (s=="IL"){
scanf("%d%d",&k,&x);
add(l[k+1],x);
}else{
scanf("%d%d",&k,&x);
add(k+1,x);
}
}
for (int i=r[0];i!=1;i=r[i]){
printf("%d ",e[i]);
}
return 0;
}
#include
#include
#include
using namespace std;
stack
int main(){
int m;
scanf("%d",&m);
while (m--){
string s;
cin>>s;
if (s=="pop"){
st.pop();
}else if (s=="query"){
printf("%d\n",st.top());
}else if (s=="empty"){
if (st.empty()){
puts("YES");
}else{
puts("NO");
}
}else{
int x;
scanf("%d",&x);
st.push(x);
}
}
return 0;
}
用法:一般用于输出每个数左边第一个比它小的数之类的问题
可以先暴力,再优化
示例:
#include
#include
using namespace std;
stack
int main(){
int n;
cin>>n;
while (n--){
int x;
cin>>x;
while (!st.empty() && st.top()>=x){
st.pop();
}
if (st.empty()){
cout<<-1<<' ';
}else{
cout<
}
st.push(x);
}
return 0;
}
#include
#include
#include
using namespace std;
queue
int main(){
int m;
cin>>m;
while (m--){
string s;
cin>>s;
if (s=="push"){
int k;
cin>>k;
q.push(k);
}else if (s=="pop"){
q.pop();
}else if (s=="query"){
cout<
}else{
if (q.empty()){
puts("YES");
}else{
puts("NO");
}
}
}
return 0;
}
用法:滑动窗口。先暴力,在优化,这里deque存下标,且一定用deque!
#include
#include
#include
using namespace std;
deque
int a[1000010];
int main(){
int n,k;
scanf("%d%d",&n,&k);
for (int i=0;i
scanf("%d",&a[i]);
}
for (int i=0;i
if (!q.empty() && i-k+1>q.back()){//注意
q.pop_back();
}
while (!q.empty() && a[q.front()]>=a[i]){
q.pop_front();
}
q.push_front(i);//注意
if (i>=k-1){
printf("%d ",a[q.back()]);//注意
}
}
puts("");
q.clear();//注意
for (int i=0;i
if (!q.empty() && i-k+1>q.back()){
q.pop_back();
}
while (!q.empty() && a[q.front()]<=a[i]){
q.pop_front();
}
q.push_front(i);
if (i>=k-1){
printf("%d ",a[q.back()]);
}
}
return 0;
}
对字符串处理的优化,一般是求模式串中出现模板串的次数,先想暴力
#include
using namespace std;
const int N=1e5+5,M=1e6+5;
char s[M],p[N];
int n,m,ne[N];//记录下一个
int main(){
cin>>n>>p+1>>m>>s+1;
for (int i=2,j=0;i<=n;i++){//i从2开始
while (j && p[i]!=p[j+1]){
j=ne[j];
}
if (p[i]==p[j+1]){
j++;
}
ne[i]=j;
}
for (int i=1,j=0;i<=m;i++){
while (j && s[i]!=p[j+1]){
j=ne[j];
}
if (s[i]==p[j+1]){
j++;
}
if (j==n){
cout<
j=ne[j];
}
}
return 0;
}
相当于字典树
#include
#include
using namespace std;
const int N=1e5+5;
char str[N];
int son[N][26],cnt[N],idx;
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(){
int t;
scanf("%d",&t);
while (t--){
char op[2];
scanf("%s%s",op,str);
if (op[0]=='I'){
insert(str);
}else{
printf("%d\n",query(str));
}
}
return 0;
}
作用:
Ⅰ将两个集合合并 Ⅱ询问两个元素是否在一个集合当中
实现:
存每个点的父节点,每个集合用一棵树来表示,树根编号即集合编号
判断树根:if (x==p[x])
求x的集合编号:while (x!=p[x]) x=p[x];
∪:p[x]=y;
优化:路径压缩。存每个点的根节点。
时间复杂度:О(1)
普通:
#include
using namespace std;
int p[100005],n,m;
void init(){
for (int i=1;i<=n;i++){
p[i]=i;
}
}
int get(int x){
if (x==p[x]){
return x;
}
return p[x]=get(p[x]);
}
void merge(int x,int y){
x=get(x);
y=get(y);
if (x!=y){
p[y]=x;
}
}
int main(){
cin>>n>>m;
init();
while (m--){
char c;
int a,b;
cin>>c>>a>>b;
if (c=='M'){
merge(a,b);
}else{
if (get(a)!=get(b)){
puts("No");
}else{
puts("Yes");
}
}
}
return 0;
}
维护每个集合size(只列出修改地方),输出sz[get(x)]
void init(){
_for(i,1,n){
p[i]=i;
sz[i]=1;
}
}
void merge(int x,int y){
x=get(x);
y=get(y);
if (x!=y){
p[y]=x;
sz[x]+=sz[y];
}
}
维护每个节点到树的距离
int get(int x){
if (p[x]!=x){
int u=get(p[x]);
d[x]+=d[p[x]];
p[x]=u;
}
return p[x];
}
概念:堆是一棵完全二叉树
作用:
堆分为大根堆和小根堆,不过都用priority_queue,
其中大根堆是priority_queue<类型>pq;
小根堆是priority_queue<类型,vector<类型>,greater<类型> >pq;
#include
#include
#include
#define _for(i,a,b) for(int i=a;i
using namespace std;
priority_queue
int main(){
int n,m;
scanf("%d%d",&n,&m);
_for(i,0,n){
int x;
scanf("%d",&x);
pq.push(x);
}
while (m--){
printf("%d ",pq.top());
pq.pop();
}
return 0;
}
up是向上调整, down是向下调整。
插入: h[++size]=x; up(size);
求min/max: h[1];
删除min/max: h[1]=h[size]; size--; down(1);
删除任意一个元素: h[k]=h[size]; size--; down(k); up(k);
修改任意一个元素: h[k]=x; down(k); up(k);
注意:左儿子为2*n,右儿子为2*n+1,堆排序从n>>1开始。
原因:n>>1是最后一个不是叶节点的节点。
#include
#include
#include
using namespace std;
const int N=1e5+5;
int h[N],sz;
void down(int u){
int t=u;
if (u*2<=sz && h[u*2]
t=u*2;
}
if (u*2+1<=sz && h[u*2+1]
t=u*2+1;
}
if (u!=t){
swap(h[u],h[t]);
down(t);
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++){
scanf("%d",&h[i]);
}
sz=n;
for (int i=n>>1;i;i--){
down(i);
}
while (m--){
printf("%d ",h[1]);
h[1]=h[sz--];
down(1);
}
return 0;
}
如果题目问删除第k个数,就直接套,代码略。
但如果题目问删除第k个插入的数,就得用映射数组了:
用ph[k]表示出现在第k次的数原下标,用hp[x]表示下标为x的数是出现第几次。
注意,是记录的下标!
#include
#include
#include
#include
using namespace std;
const int N=1e5+10;
int h[N],ph[N],hp[N],len;
void plus_c(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
void heap_swap(int x,int y){
swap(ph[hp[x]],ph[hp[y]]);
swap(hp[x],hp[y]);
swap(h[x],h[y]);
}
void down(int u){
int t=u;
if (u<<1<=len && h[u<<1]
t=u<<1;
}
if ((u<<1)+1<=len && h[(u<<1)+1]
t=(u<<1)+1;
}
if (t!=u){
heap_swap(t,u);
down(t);
}
}
void up(int u){
while (u>>1 && h[u>>1]>h[u]){
heap_swap(u,u>>1);
u>>=1;
}
}
int main(){
int n,m=0;
plus_c();
cin>>n;
while (n--){
string s;
int x,k;
cin>>s;
if (s=="I"){//插入
cin>>x;
h[++len]=x;
ph[++m]=len;
hp[len]=m;
up(len);
}else if (s=="D"){//删除
cin>>k;
k=ph[k];
heap_swap(k,len--);
down(k);
up(k);
}else if (s=="C"){//修改
cin>>k>>x;
k=ph[k];
h[k]=x;
down(k);
up(k);
}else if (s=="PM"){//求top
cout<
}else{//删除top
heap_swap(1,len--);
down(1);
}
}
return 0;
}
哈希表:一是一种数据结构,分开放寻址发和拉链法,二是字符串哈希方式
主要思想:将x∈(-1e9,1e9)变为h(x)∈(0,1e6),其中h(x)叫哈希函数
主要STL:set、multiset、map
h(x)=x%n; ,每个地址是个槽,相同地址一条链
#include
#include
using namespace std;
void plus_c(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
const int N=1e5+3;
int h[N],e[N],ne[N],idx;
void insert(int x){
int k=(x%N+N)%N;
e[idx]=x;
ne[idx]=h[k];
h[k]=idx++;
}
bool count(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(){
memset(h,-1,sizeof h);
plus_c();
int n;
cin>>n;
while (n--){
char c;
int x;
cin>>c>>x;
if (c=='I'){
insert(x);
}else if (count(x)){
puts("Yes");
}else{
puts("No");
}
}
return 0;
}
h(x)=x%n; 有了往后一个一个的填,数组要长
#include
#include
using namespace std;
const int N = 2e5 + 3;
const int null = 0x3f3f3f3f;
int h[N];
int find(int x) {
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x) {
t++;
if (t == N) {
t = 0;
}
}
return t;
}
int n;
int main() {
cin >> n;
memset(h, 0x3f, sizeof h);
while (n--) {
string op;
int x;
cin >> op >> x;
if (op == "I") {
h[find(x)] = x;
} else {
if (h[find(x)] == null) {
puts("No");
} else {
puts("Yes");
}
}
}
return 0;
}
全称字符串前缀哈希法,把字符串变成一个p进制数字(哈希值),
实现不同的字符串映射到不同的数字。
对形如 X1X2X3⋯Xn−1Xn 的字符串,
采用字符的ascii 码乘上 P 的次方来计算哈希值。
映射公式 (X1×Pn−1+X2×Pn−2+⋯+Xn−1×P1+Xn×P0)modQ
注意点:
任意字符不可以映射成0,否则会出现不同的字符串都映射成0的情况,
比如A,AA,AAA皆为0
冲突问题:通过巧妙设置P (131 或 13331) , Q (264)的值,
一般可以理解为不产生冲突。
问题是比较不同区间的子串是否相同,就转化为对应的哈希值是否相同。
求一个字符串的哈希值就相当于求前缀和,
求一个字符串的子串哈希值就相当于求部分和。
前缀和公式 h[i+1]=h[i]×P+s[i] (i∈[0,n−1] )h为前缀和数组,s为字符串数组
区间和公式 h[l,r]=h[r]−h[l−1]×Pr-l+1
区间和公式的理解: ABCDE 与 ABC 的前三个字符值是一样,只差两位,
乘上P的二次方把 ABC 变为 ABC00,
再用 ABCDE - ABC00 得到 DE 的哈希值。
#include
#include
using namespace std;
typedef unsigned long long ULL;
const int N=1e5+5,P=131;
ULL p[N],h[N];
char s[N];
ULL get(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}
int main(){
int n,m;
scanf("%d%d%s",&n,&m,s+1);
p[0]=1;
for (int i=1;i<=n;i++){
p[i]=p[i-1]*P;
h[i]=h[i-1]*P+s[i];
}
while (m--){
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if (get(l1,r1)==get(l2,r2)){
puts("Yes");
}else{
puts("No");
}
}
return 0;
}
vector, 变长数组,倍增的思想
vector
vector
size() 返回元素个数
empty() 返回是否为空
clear() 清空
front()/back()
push_back()/pop_back()
begin()/end()
[]
支持比较运算,按字典序
pair
first, 第一个元素
second, 第二个元素
支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
string,字符串
size()/length() 返回字符串长度
empty()
clear()
substr(起始下标,(子串长度)) 返回子串
c_str() 返回字符串所在字符数组的起始地址,用于scanf
queue, 队列
size()
empty()
push() 向队尾插入一个元素
front() 返回队头元素
back() 返回队尾元素
pop() 弹出队头元素
q=queue
priority_queue, 优先队列,默认是大根堆
size()
empty()
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue
stack, 栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素
deque, 双端队列
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()
[]
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size()
empty()
clear()
begin()/end()
++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 注意multimap不支持此操作。 时间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,--
bitset, 圧位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反