基础算法
快速排序
题目链接:https://www.acwing.com/problem/content/787/
分治,归并思想
#include<iostream>
using namespace std;
const int N=1e6;
int q[N];
void quick_sort(int q[],int l,int r)
{
if(l>=r)return;
int x=q[(l+r)/2],i=l-1,j=r+1;
while(i<j)
{
while(q[++i]<x);
while(q[--j]>x);
if(i<j)swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d",&q[i]);
quick_sort(q,0,n-1);
for(int i=0;i<n;i++)printf("%d ",q[i]);
return 0;
}
快速选择算法
应用:输出第k小的数
题目链接:https://www.acwing.com/problem/content/788/
与快排思想差不多,多了个剪枝
#include <iostream>
using namespace std;
const int N = 1000010;
int q[N];
int quick_sort(int q[], int l, int r, int k)
{
if(l>=r)return q[l];
int x=q[(l+r)/2],i=l-1,j=r+1;
while(i<j)
{
while(q[++i]<x);
while(q[--j]>x);
if(i<j)swap(q[i],q[j]);
}
int len=j-l+1;
if(len>=k)return quick_sort(q,l,j,k);
else return quick_sort(q,j+1,r,k-len);
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
cout << quick_sort(q, 0, n - 1, k) << endl;
return 0;
}
归并排序
#include <iostream>
using namespace std;
const int N = 1e6 + 10;//数组大小
int a[N], tmp[N];//数组和临时数组
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;//只存在一个数,一定是结果
int mid = l + r >> 1;//中间值
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);//对左右部分进行归并
//归并代码
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
//不确定那一部分结束了,都进行判断
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
//赋值给原数组
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
merge_sort(a, 0, n - 1);
for (int i = 0; i < n; i ++ ) printf("%d ", a[i]);
return 0;
}
使用归并排序寻找逆序对
#include<iostream>
using namespace std;
const int N=1000005;
int q[N],tmp[N];
long long res=0;
int n;
void merge_sort(int l,int r)
{
if(l>=r)return;//循环终止条件
int mid=l + r >> 1; //中间索引
merge_sort(l,mid),merge_sort(mid+1,r);//对左右两半进行归并
//具体归并步骤
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r){
if(q[i]<=q[j])tmp[k++]=q[i++];
else{
tmp[k++]=q[j++];
res+=mid-i+1;
}
}
//把没有放入数组的数放入数组
while(i<=mid)tmp[k++]=q[i++];
while(j<=r)tmp[k++]=q[j++];
//返回元素组
for(int i=l,k=0;i<=r;i++,k++){
q[i]=tmp[k];
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)cin>>q[i];
merge_sort(0,n-1);
cout<<res<<endl;
return 0;
}
二分法
模板
第二个模板之所以要+1是为了防止mid=l的情况,陷入死循环。
//区间[l.r]被划分成了[l,mid]和[mid+1,r]时使用
int bsearch_1(int l,int r)
{
while(l<r)
{
int mid = l+r >> 2;
if( check(mid) ) r = mid;//check判断mid右边的数是否满足条件
else l=mid+1;
}
return l;
}
//区间[l.r]被划分成了[l,mid-1]和[mid,r]时使用
int bsearch_2(int l,int r)
{
while(l<r)
{
int mid = l+r+1 >> 2;//这里要+1
if( check(mid) ) l = mid;//check判断mid左边是否满足条件
else r=mid-1;
}
return l;
}
数的范围
#include<iostream>
using namespace std;
const int N=100010;
int m,n;
int a[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)scanf("%d",&a[i]);
while(m--)
{
int x;
scanf("%d",&x);
int l=0,r=n-1;
while(l<r)
{
//[l,mid] [mid+1,r]
int mid= l+r>>1;
if(a[mid]>=x)r=mid;
else l=mid+1;
}
if(a[l]!=x)cout << "-1 -1" << endl;
else{
cout<< l << ' ';
r=n-1;l=0;
while(l<r)
{
//[l,mid-1] [mid,r]
int mid=l + r + 1 >> 1;
if(a[mid]<=x)l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
数的三次方
#include<iostream>
using namespace std;
int main()
{
double x;
scanf("%lf",&x);
double l=-1000;
double r=1000;
while(r-l>1e-8){//将答案精确多个几位更保险
double mid=(l+r)/2;
if(mid*mid*mid>x)r=mid;
else l=mid;
}
printf("%lf",l);
return 0;
}
高精算法度
高精度加法
#include<iostream>
using namespace std;
#include<vector>
vector<int> add(vector<int>&A,vector<int>&B)
{
vector<int>C;
int t=0;
for(int i=0;i<A.size()||i<B.size();i++)
{
if(i<A.size())t+=A[i];
if(i<B.size())t+=B[i];
C.push_back(t%10);
t/=10;
}
if(t>0) C.push_back(t);
return C;
}
int main()
{
string a,b;
cin>>a>>b;
vector<int>A,B;
for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--)B.push_back(b[i]-'0');
auto C=add(A,B);
for(int i=(int)C.size()-1;i>=0;i--)
{
cout<<C[i];
}
return 0;
}
高精度减法
#include<iostream>
#include<vector>
using namespace std;
//比较A,B的大小
bool cmp(vector<int>A,vector<int>B)
{
if(A.size()!=B.size())return A.size()>B.size();
else{
for(int i=A.size()-1;i>=0;i--)
{
if(A[i]!=B[i]){
return A[i]>B[i];
}
}
return true;
}
}
vector<int>sub(vector<int>&A,vector<int>&B)
{
vector<int>C;
for(int i=0,t=0;i<(int)A.size();i++)
{
t=A[i]-t;
if(i<(int)B.size())t-=B[i];
C.push_back((t+10)%10);
if(t<0)t=1;
else t=0;
}
while(C.size()>1&&C.back()==0)C.pop_back();
return C;
}
int main()
{
string a,b;
cin>>a>>b;
vector<int>A,B;
for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--)B.push_back(b[i]-'0');
if(cmp(A,B)){//A>B
auto C = sub(A,B);
for(int i=C.size()-1;i>=0;i--)cout<<C[i];
}else{//A<B
auto C = sub(B,A);
cout<<"-";
for(int i=C.size()-1;i>=0;i--)cout<<C[i];
}
return 0;
}
高精度乘法
高精度x低精度
#include<iostream>
#include<vector>
using namespace std;
vector<int>mul(vector<int>&A,int b)
{
vector<int>C;
int t=0;
for(int i=0;i<(int)A.size();i++)
{
t=t+A[i]*b;
C.push_back(t%10);
t/=10;
}
//把剩余的数字放入数组
while(t>0)
{
C.push_back(t%10);
t/=10;
}
//去除前导0.如1213*0
while(C.size()>1&&C.back()==0)C.pop_back();
return C;
}
int main()
{
string a;
int b;
cin>>a>>b;
vector<int>A;
for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
auto C=mul(A,b);
for(int i=C.size()-1;i>=0;i--)cout<<C[i];
return 0;
}
高精度除法
高精度/低精度
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>div(vector<int>&A,int b,int &r)
{
vector<int>C;
for(int i=A.size()-1;i>=0;i--)
{
r=A[i]+r*10;
C.push_back(r/b);
r%=b;
}
reverse(C.begin(),C.end());//倒转,把顺序改为逆序,
while(C.size()>1&&C.back()==0)C.pop_back();//去除前导0
return C;
}
int main()
{
string a;
int b,r=0;
cin>>a>>b;
vector<int>A;
for(int i=a.size()-1;i>=0;i--)A.push_back(a[i]-'0');//逆序
auto C=div(A,b,r);
for(int i=C.size()-1;i>=0;i--)cout<<C[i];//逆序输出
cout<<endl<<r<<endl;//余数
return 0;
}
前缀和差分
前缀和和差分互逆,S[n]=a1+a2+...+a[n],那么S为a的前缀和,a为s的差分
一维前缀和
用于求任意区间的和
Sn=a[1]+...+a[n];
S(l-r)=S[r]=S[l-1];
#include<iostream>
using namespace std;
const int N=100010;
int q[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&q[i]);
}
for(int i=1;i<=n;i++)q[i]=q[i-1]+q[i];
for(int i=1;i<=m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",q[r]-q[l-1]);
}
return 0;
}
二维前缀和
左上角和右下角两点之间的矩形内的所有数和
#include<iostream>
using namespace std;
const int N=1010;
int a[N][N];
int main()
{
int m,n,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
//前缀和
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+a[i][j];
//每个矩形的和
while(q--)
{
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1]);
}
return 0;
}
一维差分
#include<iostream>
using namespace std;
const int N=100010;
//a原数组,b差分数组
int a[N],b[N];
//对差分数组进行插入操作,可将原数组[l,r]之间的没个数都+c
void insert(int l,int r,int c)
{
//对差分数组l和r+1进行+c操作,相当于原数组,[l,r]区间都加上c
b[l]+=c;
b[r+1]-=c;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
//原数组
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
//求a数组的差分,相当于对前缀和和差分都为0的数组进行n次插入操作
for(int i=1;i<=n;i++)insert(i,i,a[i]);
//对b数组(a的差分数组)进行修改
while(m--){
int l,r,c;
scanf("%d%d%d",&l,&r,&c);
insert(l,r,c);
}
//对b数组求前缀和就是a被修改后的数组
for(int i=1;i<=n;i++)b[i]=b[i-1]+b[i];
for(int i=1;i<=n;i++)printf("%d ",b[i]);
return 0;
}
二维差分
#include<iostream>
using namespace std;
const int N=1010;
int a[N][N],b[N][N];
//修改差分数组,从而实现对原数组内某个矩形的所有值+c,
//最后只需对差分数组求前缀和就可得到修改后的数组
void insert(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1]+=c;
b[x1][y2+1]-=c;
b[x2+1][y1]-=c;
b[x2+1][y2+1]+=c;
}
int main()
{
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
//构造a的差分数组b
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
insert(i,j,i,j,a[i][j]);
//对差分数组进行修改
while(q--)
{
int x1,y1,x2,y2,c;
cin>>x1>>y1>>x2>>y2>>c;
insert(x1,y1,x2,y2,c);
}
//差分求和,求出修改后的原数组
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
//打印
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) printf("%d ",b[i][j]);
cout<<endl;
}
return 0;
}
双指针
最长连续不重复区间
#include<iostream>
using namespace std;
const int N=100010;
int mp[N],a[N];
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
int res=0;
for(int i=0,j=0;i<n;i++)
{
//记入已经在i-j之间的数字
mp[a[i]]++;
//去重
while(mp[a[i]]>1)
{
mp[a[j]]--;
j++;
}
res=max(i-j+1,res);
}
cout<<res;
return 0;
}
数组元素的目标和
#include<iostream>
using namespace std;
const int N=100010;
int a[N],b[N];
int n,m,x;
int main()
{
cin>>n>>m>>x;
for(int i=0;i<n;i++)cin>>a[i];
for(int j=0;j<m;j++)cin>>b[j];
for(int i=0,j=m-1;i<n;i++)
{
while(j>=0&&a[i]+b[j]>x)j--;
if(a[i]+b[j]==x)
{
cout<<i<<" "<<j<<endl;
break;
}
}
return 0;
}
判断子序列
#include<iostream>
using namespace std;
const int N=100010;
int a[N],b[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<m;i++)cin>>b[i];
int i=0,j=0;
while(i<n&&j<m)
{
if(a[i]==b[j])i++;
j++;
}
if(i>=n)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
位运算
n的二进制第k位数字 n>>k&1
获取第k位数字
#include<iostream>
using namespace std;
int main()
{
int a = 10;
//3210
//1010 最后以为为0,以此类推
// 101 //右移一位
//&001 将个为前面的数字全部变为0,若最后一位为0就是0,是1就是1
// 001=>1
cout << (a >> 1 & 1) << endl;//1
}
将一个数转化为二进制
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int k = 0;
int b = a;
while (b) {
b /= 2;
k++;
}
k--;
for(int k=3;k>=0;k--)
cout << (a >> k & 1) ;
}
n的二进制中最后一位1 x&(~x+1)
将二进制中的最后一位1保留,其余位全部变为0
二进制中1的个数
#include<iostream>
using namespace std;
int n;
//x&-x=>x&(~x+1)
//10=>1010=>0110=>0010
int lowbit(int x)
{
return x & -x;
}
int main()
{
cin>>n;
while(n--)
{
int x;
cin>>x;
//如果最后一位1存在
int res=0;
while(x)
{
// 1010
//- 0010
// 1000 运算一次少一个1,减多少次就有几个1
x-=lowbit(x);
res++;
}
cout<<res<<" ";
}
return 0;
}
离散化
值域很大,但数字分布很稀疏
区间合并
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int,int> PII;
vector<PII>segs;
void merge()
{
//接收答案
vector<PII>res;
//左端点排序
sort(segs.begin(),segs.end());
//标记区间的左右端点
int st=-2e9,ed=-2e9;
for(auto seg:segs)
{
if(seg.first>ed)
{
//如果大于右端点,则是一段新区间,把前一段区间放入集合,if语句排除第一个空区间
if(st!=-2e9)res.push_back({st,ed});
//更新左右端点
st=seg.first;ed=seg.second;
}else ed=max(ed,seg.second);//扩大区间
}
//如果不是空集合,把最后一段区间加入集合
if(st!=-2e9)res.push_back({st,ed});
//把结果赋给原集合
segs=res;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
int l,r;
scanf("%d%d",&l,&r);
segs.push_back({l,r});
}
merge();
cout<<segs.size()<<endl;
return 0;
}
数据结构
数组模拟链表,中途被删去的结点的空间不用考虑不再使用
单链表
#include<iostream>
using namespace std;
const int N=100010;
/*
head 头节点
e[i]储存值
ne[i]储存下一个节点下标
idx 表示空结点,可以插入值
*/
int head,e[N],ne[N],idx;
//初始化
void init()
{
head=-1;//初始化,头节点为-1
idx=0;//空闲结点数组下标为0
}
//头插
void add_to_head(int x)
{
e[idx]=x;//在新节点储存值
ne[idx]=head;//新节点的下一个结点,改为头节点下一个结点
head=idx;//头结点指向新节点
idx++;。、//空结点后移
}
//在第k个数后面插入
void add(int k,int x)
{
e[idx]=x;//在新节点储存值
ne[idx]=ne[k];//新节点的下一个结点,改为第k个结点下一个结点
ne[k]=idx;//第k个结点的下一个结点改为新节点
idx++;//空结点后移
}
//在第k个数后面删除
void remove(int k)
{
ne[k]=ne[ne[k]];//第k个结点直接指向下下个结点
}
int main()
{
init();
int n;
cin>>n;
while(n--)
{
int k,x;
char op;
cin>>op;
if(op=='H')
{
cin>>x;
add_to_head(x);
}
else if(op=='D')
{
cin>>k;
if(k==0)head=ne[head];//如果k=0,则删除第一个结点
else remove(k-1);//第k个节点子数组中的下标为k-1,0为第一个结点
}
else
{
cin>>k>>x;
add(k-1,x);//第k个节点子数组中的下标为k-1,0为第一个结点
}
}
for(int i=head;i!=-1;i=ne[i]) cout<<e[i]<<" ";
return 0;
}
双链表
#include<iostream>
using namespace std;
const int N=100010;
int e[N],l[N],r[N],idx;
//初始化
void init()
{
r[0]=1;//索引0为头节点
l[1]=0;//索引1为末尾结点,所以下标为1时结束
idx=2;//空结点从2开始
}
void remove(int k)
{
r[l[k]]=r[k];//把k结点的前一个结点的右指针指向k节点的下一个结点
l[r[k]]=l[k];//把k结点的右节点的左指针指向k结点的左结点
}
void add(int k,int x)
{
e[idx]=x;//给空结点赋值
l[idx]=k;//新节点的左指针指向k结点
r[idx]=r[k];//新节点的右节点指向k结点的下一个结点
l[r[k]]=idx;//k结点的下一个结点的左指针指向新节点
r[k]=idx;//k结点的右指针指向新节点
idx++;//指向下一个空结点
}
int main()
{
init();
int n;
cin>>n;
while(n--)
{
int k,x;
string op;
cin>>op;
//第一个数从索引2开始,所以第k个数的下标索引为k+1
if(op=="L"){//在最左端插入就是在头节点的右边插入
cin>>x;
add(0,x);
}else if(op=="R"){//在最右边插入就是在末尾结点的左边结点后面插入
cin>>x;
add(l[1],x);
}else if(op=="D"){//删除
cin>>k;
remove(k+1);
}else if(op=="IL"){//在k结点的左边插入实际上实在k结点的上一个结点的右边插入
cin>>k>>x;
add(l[k+1],x);
}else{//在k结点的右边插入
cin>>k>>x;
add(k+1,x);
}
}
//打印数组,i=1结束
for(int i=r[0];i!=1;i=r[i])cout<<e[i]<<" ";
cout<<endl;
return 0;
}
栈
模拟栈
#include<iostream>
using namespace std;
const int N=100010;
//栈数组,栈顶坐标
int stk[N],tt;
//入栈
void push(int x)
{
stk[++tt]=x;
}
//出栈
void pop()
{
if(tt>0)tt--;
}
//是否为空
bool isEmpty()
{
return tt==0;
}
//返回栈顶元素
int query()
{
return stk[tt];
}
int main()
{
int n;
cin>>n;
while(n--)
{
int x;
string op;
cin>>op;
if(op=="push"){
cin>>x;
push(x);
}else if(op=="pop"){
pop();
}else if(op=="query"){
cout<<query()<<endl;
}else{
if(isEmpty())cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
return 0;
}
表达式运算
#include<iostream>
using namespace std;
#include<stack>
#include<algorithm>
#include<unordered_map>
#include<string>
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;//最后还得i++;所以i=j-1,i++之后重新变为j
}else if(s[i]=='('){//如果为左括号直接入栈
op.push(s[i]);
}else if(s[i]==')'){//如果为右括号,把括号内的数字进行运算
while(op.top()!='(')//循环运算,知道遇到左括号
{
eval();
}
op.pop();//左括号出栈
}else{
//如果新的运算符优先级低于栈内优先级,则将栈内运算符进行运算
while(op.size()&&h[s[i]]<=h[op.top()])eval();
op.push(s[i]);//将新的运算符入栈
}
}
while(op.size())eval();//将栈内没有运算的的数进行运算
cout<<num.top()<<endl;//输出结果
return 0;
}
模拟队列
#include<iostream>
using namespace std;
const int N=100010;
//队列数组,队首索引,队尾索引
int q[N],hh,tt=-1;
//入栈
void push(int x)
{
q[++tt]=x;
}
//出栈
void pop()
{
if(hh<=tt)hh++;
}
//是否为空
bool isEmpty()
{
return hh>tt;//队首索引大于队尾索引则为空
}
//返回栈顶元素
int query()
{
return q[hh];
}
int main()
{
int n;
cin>>n;
while(n--)
{
int x;
string op;
cin>>op;
if(op=="push"){
cin>>x;
push(x);
}else if(op=="pop"){
pop();
}else if(op=="query"){
cout<<query()<<endl;
}else{
if(isEmpty())cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
return 0;
}
单调栈
#include<iostream>
using namespace std;
const int N=100010;
//单调递增的栈
int stk[N],tt;
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
while(tt&&stk[tt]>=x)tt--;//如果栈顶元素大于新元素,直接pop
if(tt)cout<<stk[tt]<<' ';//循环结束后,新元素前面要么没元素,要么就比新元素小,所以栈不为空,那么栈顶元素就是左边最近的更小元素
else cout<<-1<<" ";//如如果为空则不存在比新元素小的数,输出-1
stk[++tt]=x;//将新元素入栈
}
return 0;
}
滑动窗口(优先队列)
数组模拟队列
#include<iostream>
using namespace std;
const int N=1000010;
//优先队列--单调递增,队列保存下标
int q[N],hh,tt,a[N];
int n,k;
int main()
{
cin>>n>>k;
//给数组赋值
for(int i=0;i<n;i++)cin>>a[i];
//求最小值
hh=0,tt=-1;//初始化队列
for(int i=0;i<n;i++)
{
if(hh<=tt&&q[hh]<i-k+1)hh++;//如果队头不在窗口内,pop出队列
while(hh<=tt&&a[q[tt]]>=a[i])tt--;//如果队尾元素大于新元素,pop出队列
q[++tt]=i;
if(i>=k-1)cout<<a[q[hh]]<<" ";
}
cout<<endl;
//求最大值
hh=0;tt=-1;//初始化队列
for(int i=0;i<n;i++)
{
if(hh<=tt&&q[hh]<i-k+1)hh++;//如果队头不在窗口内,pop出队列
while(hh<=tt&&a[q[tt]]<=a[i])tt--;//如果队尾元素大于新元素,pop出队列
q[++tt]=i;
if(i>=k-1)cout<<a[q[hh]]<<" ";
}
return 0;
}
kmp算法
#include <iostream>
using namespace std;
const int N = 1000010;
char p[N], s[N]; // 用 p 来匹配 s
// “next” 数组,若第 i 位存储值为 k
// 说明 p[0...i] 内最长相等前后缀的前缀的最后一位下标为 k
// 即 p[0...k] == p[i-k...i]
int ne[N];
int n, m; // n 是模板串长度 m 是模式串长度
int main()
{
cin >> n >> p >> m >> s;
// p[0...0] 的区间内一定没有相等前后缀
ne[0] = -1;
// 构造模板串的 next 数组
for (int i = 1, j = -1; i < n; i ++)
{
while (j != -1 && p[i] != p[j + 1])
{
// 若前后缀匹配不成功
// 反复令 j 回退,直至到 -1 或是 s[i] == s[j + 1]
j = ne[j];
}
if (p[i] == p[j + 1])
{
j ++; // 匹配成功时,最长相等前后缀变长,最长相等前后缀前缀的最后一位变大
}
ne[i] = j; // 令 ne[i] = j,以方便计算 next[i + 1]
}
// kmp start !
for (int i = 0, j = -1; i < m; i ++)
{
while (j != -1 && s[i] != p[j + 1])
{
j = ne[j];
}
if (s[i] == p[j + 1])
{
j ++; // 匹配成功时,模板串指向下一位
}
if (j == n - 1) // 模板串匹配完成,第一个匹配字符下标为 0,故到 n - 1
{
// 匹配成功时,文本串结束位置减去模式串长度即为起始位置
cout << i - j << ' ';
// 模板串在模式串中出现的位置可能是重叠的
// 需要让 j 回退到一定位置,再让 i 加 1 继续进行比较
// 回退到 ne[j] 可以保证 j 最大,即已经成功匹配的部分最长
j = ne[j];
}
}
return 0;
}
Trie树
Trie树
Trie树中有个二维数组 son[N][26],表示当前结点的儿子,如果没有的话,可以等于++idx。Trie树本质上是一颗多叉树,对于字母而言最多有26个子结点。所以这个数组包含了两条信息。比如:son[1][0]=2表示1结点的一个值为a的子结点为结点2;如果son[1][0] = 0,则意味着没有值为a子结点。这里的son[N][26]相当于链表中的ne[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] ++; // cnt相当于链表中的e[idx]
}
Trie字符串统计
#include<iostream>
using namespace std;
const int N=100010;
//son记录下一个结点的值,cn,记录这个结点是否是某个字符串的结尾字符,值代表个数,idx代表下一个空结点
int son[N][26],cn[N],idx;//下标0既是根结点又是空结点
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的子节点
}
cn[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];//p指向儿子结点
}
return cn[p];//返回字符串个数
}
int main()
{
int n;
cin>>n;
while(n--)
{
char op[2];
scanf("%s%s",op,str);//输入字符串
if(op[0]=='I')insert(str);//获取第一个字符,判断是什么操作
else cout<<query(str)<<endl;//输出结果
}
return 0;
}
最大异或对
#include<iostream>
using namespace std;
const int N=100010,M=31*N;
int n,a[N];
int son[M][2],idx;
//插入
void insert(int x)
{
int p=0;
for(int i=30;i>=0;i--)//总共31位,0-30
{
int u=x>>i&1;//获取第i位数字
if(!son[p][u])son[p][u]=++idx;//如果结点不存在,开辟新节点
p=son[p][u];//指向儿子结点
}
}
int search(int x)
{
int p=0,res=0;//res记录大小
for(int i=30;i>=0;i--)//总共31位,0-30
{
int u=x>>i&1;//获取第i位数字
if(son[p][!u])//若不同数字存在,则该位结果为1,比如二进制位的数字为1,若存在为0的数字,则最大结果为1
{
p=son[p][!u];//进入另一个分支,遇到不同数字概率更大
res=res*2+1;//该位结果位1
}else{//若不存在不同的数字,则结果为0
p=son[p][u];//只能进入本分支
res=res*2+0;//该位结果为0
}
}
return res;//返回最大结果
}
int main()
{
cin>>n;
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]));//查询
cout<<res<<endl;//输出结果
return 0;
}
并查集
合并集合
#include<iostream>
using namespace std;
const int N=100010;
int n,m,p[N];
//查找根节点
int find(int x)
{
if(p[x]!=x)p[x]=find(p[x]);//如果不是根结点,则一直向上寻找,并将所有途经结点指向根节点,使下一次查找变为O(1)
return p[x];//返回根节点的值
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)p[i]=i;//一开始每个元素都是一个集合,所以都是根节点
while(m--)
{
char op;int a,b;
cin>>op>>a>>b;
if(op=='M')p[find(a)]=find(b);
else{
if(find(a)==find(b))cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
return 0;
}
连通块中点的数量
#include<iostream>
using namespace std;
const int N=100010;
int p[N],Size[N],n,m;
//查找根节点
int find(int x)
{
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
p[i]=i;
Size[i]=1;
}
while(m--)
{
char op[5];int a,b;
scanf("%s",op);
if(op[0]=='C')//合并集合
{
cin>>a>>b;
int x=find(a),y=find(b);//查找a,b的根节点
if(x==y)continue;//如果在同一个集合,不用合并
Size[x]+=Size[y];
p[y]=p[x];
}else if(op[1]=='1'){//判断a,b是否在一个集合
cin>>a>>b;
if(find(a)==find(b))puts("Yes");
else puts("No");
}else{//输出a所在集合大小
cin>>a;
cout<<Size[find(a)]<<endl;
}
}
return 0;
}
食物链
#include<iostream>
using namespace std;
const int N=50010;
int p[N],d[N];
int n,k;
int find(int x)
{
if(p[x]!=x)
{
int t=find(p[x]);//储存根节点
d[x]+=d[p[x]];//到上一个节点的距离+上一个结点到根节点的距离=本结点到根节点的距离
p[x]=t;//指向根节点;
}
return p[x];
}
int main()
{
cin>>n>>k;
int res=0;
for(int i=1;i<=n;i++)p[i]=i;
while(k--)
{
int t,x,y;
cin>>t>>x>>y;
if(x>n||y>n)res++;
else {
int py=find(y),px=find(x);
if(t==1)
{
if(px==py&&(d[x]-d[y])%3)res++;//如果二者在同一个集合但到根节点的距离不相等,所以不是同类
else if(px!=py){//如果不在一个集合,和前面的为真话不冲突,所以为真,按所给关系,合并集合
p[px]=p[y];//x的根节点指向y的根节点
d[px]=d[y]-d[x];//因为x,y同类,所以dx+d[px]-dy=0,则d[px]=dy-dx
}
}else{
if(px==py&&(d[x]-d[y]-1)%3)res++;//如果二者在同一个集合但到根节点的距离差不相等1,所以不是同类
else if(px!=py)
{
p[px]=py;//x的根节点指向y的根节点
d[px]=d[y]+1-d[x];//因为x吃y,所以dx+d[px]-dy-1=0,则d[px]=dy+1-dx
}
}
}
}
cout<<res<<endl;
}
堆排序
堆排序
#include<iostream>
using namespace std;
const int N=100010;
int n,m;
int h[N],Size;
void down(int u)
{
int t=u;//记录根的值
if(u*2<=Size&&h[u*2]<h[t])t=u*2;//如果左儿子存在,并且小于根节点,则进行标记
if(u*2+1<=Size&&h[u*2+1]<h[t])t=u*2+1;//如果右儿子存在,则与标记结点进行比较,若右节点更小,则进行标记
if(u!=t)//如果被标记的的点(最小的儿子结点)不是根节点,说明存在一个值比根节点小,两个值进行交换
{
swap(h[u],h[t]);
down(t);//将下移的值与他的儿子结点继续比较,直至符合小顶堆的规则
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>h[i];
Size=n;
for(int i=n/2;i;i--)down(i);//将数组维护成一个小顶堆
while(m--)
{
cout<<h[1]<<' ';//输出堆顶的值
h[1]=h[Size];//将最后一个值赋给堆顶
Size--;//大小-1
down(1);//向下移动,直至符合规则
}
return 0;
}
模拟堆
考试不需要,直接用优先队列,了解就行
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = 100010;
int h[N], ph[N], hp[N], cnt;
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
int main()
{
int n, m = 0;
scanf("%d", &n);
while (n -- )
{
char op[5];
int k, x;
scanf("%s", op);
if (!strcmp(op, "I"))
{
scanf("%d", &x);
cnt ++ ;
m ++ ;
ph[m] = cnt, hp[cnt] = m;
h[cnt] = x;
up(cnt);
}
else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
else if (!strcmp(op, "DM"))
{
heap_swap(1, cnt);
cnt -- ;
down(1);
}
else if (!strcmp(op, "D"))
{
scanf("%d", &k);
k = ph[k];
heap_swap(k, cnt);
cnt -- ;
up(k);
down(k);
}
else
{
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
up(k);
down(k);
}
}
return 0;
}
哈希表
可以不掌握,直接用set集合
模拟散列表
#include <cstring>
#include <iostream>
using namespace std;
const int N = 200003, 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 main()
{
memset(h, 0x3f, sizeof h);
int n;
scanf("%d", &n);
while (n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x);
if (*op == 'I') h[find(x)] = x;
else
{
if (h[find(x)] == null) puts("No");
else puts("Yes");
}
}
return 0;
}
拉链法
#include <cstring>
#include <iostream>
using namespace std;
const int N = 100003;
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 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;
scanf("%d", &n);
memset(h, -1, sizeof h);
while (n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x);
if (*op == 'I') insert(x);
else
{
if (find(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
字符串哈希
#include <iostream>
#include <algorithm>
using namespace std;
typedef unsigned long long ULL;
const int N = 100010, P = 131;
int n, m;
char str[N];
ULL h[N], p[N];
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
int main()
{
scanf("%d%d", &n, &m);
scanf("%s", str + 1);
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
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;
}
搜索和图论
dfs
排列数字
#include<iostream>
using namespace std;
const int N=10;
int path[N],n;
bool st[N];//记录数字是否被用过
void back(int u)//u记录path数组的下表
{
if(u==n)//所有数字用完
{
for(int i=0;i<n;i++)
{
cout<<path[i]<<" ";
}
cout<<endl;
}
for(int i=1;i<=n;i++)
{
if(!st[i])//如果数字么被用过
{
path[u]=i;
st[i]=true;
back(u+1);
st[i]=false;
}
}
}
int main()
{
cin>>n;
back(0);
return 0;
}
动态规划
背包问题
01背包
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N];
int w[N],v[N];
int n,m;
int dp[N][N];
/*
dp数组的含义:dp[i][j]
在物品数为i,容积为j时,所装物品的最大价值
*/
int main()
{
//二维
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j<v[i])dp[i][j]=dp[i-1][j];
else dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
}
cout<<dp[n][m];
//一维
// cin>>n>>m;
// for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
// for(int i=1;i<=n;i++)
// for(int j=m;j>=v[i];j--)
// f[j]=max(f[j],f[j-v[i]]+w[i]);
// cout<<f[m];
return 0;
}