//本题考查链表的应用
//链表相对于数组的优点:在中间进行元素的插入和删除更加方便,仅需O(1)的时间复杂度
//由于本题频繁进行元素直接的位置调换,故应使用链表完成
//为了方便起见,使用数组模拟双向链表
//使用链表需要注意的是插入结点和删除结点的操作顺序以及指针的链接方法
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+9;
int nex[N];//nex[i]存储第i个结点的后继结点编号
int pre[N];//pre[i]存储第i个结点的前驱结点编号
void Delete(int x)//删除x结点
{
if(nex[x]==-1)//若x是最后一个结点(尾结点)
{
nex[pre[x]]=-1;//则将x的上一个结点的nex指针赋为-1,使其变成新的尾结点
return;
}
//x不是尾结点
nex[pre[x]]=nex[x];//令x的前驱结点的nex指针指向x的后继结点
pre[nex[x]]=pre[x];//令x的后继结点的pre指针指向x的前驱结点
}
void Insert_front(int y,int x)//把x插入到y的前面
{
nex[x]=y;//x的后继结点改为y
pre[x]=pre[y];//x的前驱指针指向y的前驱结点
pre[nex[x]]=x; //x的后继结点的前驱指针指向x
nex[pre[x]]=x;//x的前驱结点的后继指针指向x
//注意以上顺序不可随意颠倒
}
void Insert_back(int y,int x)//把x插入到y的后面
{
if(nex[y]==-1)//如果y是尾结点
{
nex[y]=x;//y的后继指针指向x
pre[x]=y;//x的前驱指针指向y
nex[x]=-1;//x的后继指针指向-1
}
else//y不是尾结点
{
nex[x]=nex[y];//x的后继指针指向y的后继结点
pre[x]=y;//x的前驱指针指向y
nex[y]=x;//y的后继指针指向x
pre[nex[x]]=x;//x的后继结点的前驱指针指向x
//nex[x]已经变为nex[y]且nex[y]后面已经更改
//注意以上顺序不可随意颠倒
}
}
void Print_list()//遍历链表
{
for(int i=nex[0];i!=-1;i=nex[i])//i随着next指针不断迭代直至-1
{
cout<<i<<" ";
}
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)//初始化每个结点的后继指针和前驱指针
{
nex[i]=i+1;//第i个结点的后继是i+1
pre[i]=i-1;//第i个结点的前驱是i-1
}
nex[0]=1;//头结点指向编号1开始
nex[n]=-1;//尾结点的next指针置为-1
for(int i=1;i<=m;i++)//m条指令
{
int x,y,z;
cin>>x>>y>>z;
if(z)//将x插入到y的前面
{
Delete(x);//先删除
Insert_front(y,x);//再重新插入
}
else//将x插入到y的后面
{
Delete(x);//先删除
Insert_back(y,x);//再重新插入
}
}
Print_list();//最后遍历链表并输出
cout<<'\n';
return 0;
}
//本题考查栈在括号匹配中的应用
//括号匹配的基本规则是一个左括号匹配一个右括号,且左括号在右括号之前
//故依次读入字符,并执行如下操作:
//1、读入的是左括号,说明新加入了一个更紧急待匹配的字符,将其压栈
//2、读入的是右括号,其可令最紧迫的任务得以消解(匹配最新的左括号),故令一个左括号出栈
//3、若此时栈为空,出栈失败,则匹配失败
//4、读入所有字符后再次检查栈,若栈非空,说明其中还有未匹配的左括号,则匹配失败
//为了方便起见,本题使用数组和栈顶指针来模拟栈
#include <bits/stdc++.h>
using namespace std;
const int N=1e3;
char stk[N];//栈
int top=0;//栈顶指针,空栈的栈顶指针为0
int main(){
int n;
bool flag=1;//标志位表示能否成功匹配
cin>>n;
for(int i=1;i<=n;i++)//输入n个括号
{
char c;cin>>c;
if(c=='(')//如果输入左括号
{
stk[++top]=c;//则入栈
}
else//若输入右括号
{
if(top==0)//栈空,找不到左括号与之匹配
{
flag=0;//不能成功匹配
}
else//栈不空
{
top--;//左括号出栈
}
}
}
if(top)flag=0;//全部处理完毕,栈仍非空,还有剩余的左括号
if(flag)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N=1006;
int m,n;//内存容量 文章长度
int q[N],hh=1,tt=0;//理解一下为什么这么设置
int main(){
cin>>m>>n;
int ans=0;
for(int i=1;i<=n;i++){
int num;cin>>num;
bool tag=0;//在内存中无匹配
for(int j=hh;j<=tt;j++){//数组实现的可以遍历
if(q[j]==num)tag=true;
}//找到了内存中有这个
// if(find(q+hh,q+tt+1,num)!=q+tt+1)tag=1;//必须是q+tt+1不能不写
if(tag)continue;
if(tt-hh+1==m)hh++;//满内存了,队头出队
q[++tt]=num;//队尾加元素
ans++;
}
cout<<ans<<'\n';
return 0;
}
解释型:
//本题考查队列的应用
//读入n个非负整数并同时在队列中查询该元素
//若能查到说明不用将其再次保存,无需进行任何操作
//若查不到,则将其入队
//若入队时队列已满,则将队首元素出队再将其入队
//入队一次则计数值加1,最后输出计数值即可
//为了方便起见,使用数组和首尾指针模拟队列
#include <bits/stdc++.h>
using namespace std;
const int N=1e5;
int que[N];//队列
int head=1,tail=0;//head为头指针,tail为尾指针
//初始时尾指针在头指针前面,若判空则为tail-head+1==0,判满为taile-head+1==m
int main()
{
int n,m;
int ans=0;
cin>>m>>n;
for(int i=1;i<=n;i++)//输入n个非负整数
{
int x;
cin>>x;
bool flag=0;//flag=1说明队列中已存在此元素,无需再入队
for(int j=head;j<=tail;j++)//在队列中查找元素x
{
if(que[j]==x)//找到
{
flag=1;
break;
}
}
if(flag==0)//若未找到,需要将其入队
{
if(tail-head+1==m)//队列已满
{
head++;//队首出队,头指针后移实现
}
que[++tail]=x;//加入队尾
ans++;//计数值加1
}
}
cout<<ans<<endl;//输出最终计数值
return 0;
}
//本题可作为优先队列(堆)的一道模板题
//由于数据量较大,如果暴力查询和取模可能会超时
//由于优先队列的插入和删除是log(n)级别,因此可以节省大量时间
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+100;
priority_queue<ll>que;//优先队列(大根堆)
int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,q;
ll sum=0;//存储数组元素和
cin>>n>>q;
for(int i=1;i<=n;i++)//输入n个元素
{
int x;
cin>>x;
sum+=x;//计算总和
que.push(x);//同时插入大根堆
}
for(int i=1;i<=q;i++)//q次操作
{
int x;cin>>x;//输入本次的模值
while(que.top()>=x)//对大根堆中所有大于模值的元素取模
{
ll y=que.top();//获取堆顶元素
que.pop();//出队
sum=sum-y;//先将其减去
sum=sum+y%x;//取模后再累加
que.push(y%x);//重新入队,此时y%x已经小于x
//每循环一次,就将大根堆中大于等于x的元素取模、累加、重新入队,效率更高
}
cout<<sum<<" ";//处理完一轮,输出本次的sum
}
return 0;
}
手写堆:
//本题可以作为手写堆的训练模板,本题以大根堆为例
//堆的本质是一种用数组存储的二叉树,且满足根结点一定大(小)于左右子结点
//对于结点i,其左孩子编号为2*i,右孩子编号为2*i+1
//堆的核心操作是入堆(push)和出堆(pop),前者需要自下而上地调整堆,后者需要自上而下地调整堆
//实现一个完整的堆数据结构,核心在于写出自下而上的调整函数和自上而下的调整函数
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+100;
ll heap[N];//堆
int heap_size=0;//堆的大小,初始化为0表示空堆
void push_up(int x)//从x开始自下而上地调整堆,x是新插入的结点
{
while(x!=1)//当x不为根结点
{
if(heap[x]>heap[x/2])//若x大于其父结点,则向上更新
{
swap(heap[x],heap[x/2]);//交换x与其父结点的值
x=x/2;//x向上追溯
}
else break;//直至x不比其父亲大为止,退出
}
}
void push(int x)//在堆中插入结点x
{
heap[++heap_size]=x;//先把x插入堆的最末尾
push_up(heap_size);//再自下而上的调整
}
void push_down(int root)//从root开始自上而下地调整堆
{
int left=2*root;//取其左孩子编号
int right=2*root+1;//取其右孩子编号
if(left>heap_size)return;//若root没有左孩子和右孩子,直接退出
if(right>heap_size)//若右孩子不存在(只有左孩子)
{
if(heap[left]>heap[root])//若左孩子更大
{
swap(heap[left],heap[root]);//交换之
push_down(left);//以左孩子为新根结点,递归操作
}
}
else//若左右孩子均存在
{
if(heap[root]==max(heap[root],max(heap[left],heap[right])))
{
//当前根结点最大,无需操作
return;
}
if(heap[left]>heap[right])//根结点不是最大,且左孩子大于右孩子
{
swap(heap[root],heap[left]);//左孩子与根结点交换之
push_down(left);//以左孩子为新根结点,递归操作
}
else//根结点不是最大,且右孩子大于左孩子
{
swap(heap[root],heap[right]);//右孩子与根结点交换之
push_down(right);//以右孩子为新根结点,递归操作
}
}
}
void pop()//从堆中删除堆顶元素
{
heap[1]=heap[heap_size--];//先把最末尾元素填入堆顶
push_down(1);//再自上而下地调整
}
ll top()//取堆顶元素
{
return heap[1];
}
int main()
{
int n,q;
ll sum=0;
cin>>n>>q;
for(int i=1;i<=n;i++)//n组数据
{
int x;
cin>>x;
sum+=x;//求和
push(x);//依次插入堆中
}
for(int i=1;i<=q;i++)//q次操作
{
int x;
cin>>x;
while(top()>=x)//对堆中所有大于等于模值x的元素进行操作
{
ll y=top();//取出该元素
pop();//删除
sum=sum-y;//先从总和中减去该元素
sum=sum+y%x;//取模后重新累加
push(y%x);//重新插入堆中
//完成一轮循环,即对数组中的每个元素都实现了对x取模
}
cout<<sum<<" ";//输出本次的数组和即可
}
return 0;
}