写在前面:所有模板均来自acwing
算法
排序
快排
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;//跳出条件
int i = l - 1, j = r + 1, x = q[l];
//下方是D——W因此需预留偏移量
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
//一次处理仅能将数组划分为满足以x为界限的排序,而我们要的是整个数组从小到大的排序因此需要递归处理
}
sort排序是优化后的快排,所以更快,但有些题目不仅仅要排序后的结果,因此掌握这种思想很有必要
归并排序
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];
}
}
前缀和
用于计算数组的子集区间长度和
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
int l,r;
cin>>l>>r;
result=s[r]-s[l-1];
1.s[0]=0;
2.数组从1开始;
3.s[r]-s[l-1]表示从L到R的元素和
高精度
高精度加法
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;//存放结果
int t = 0;//进位
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;//进位与否
}
if (t) C.push_back(t);//判断最高位要不要进1
return C;
}
一道高精度加法的完整代码
#include<bits/stdc++.h>
using namespace std;
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)C.push_back(1);
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');
}
vector<int> C=add(A,B);
for(int i=C.size()-1;i>=0;i--)
{
cout<<C[i];
}
return 0;
}
高精度减法
给定两个正整数(不含前导 00),计算它们的差,计算结果可能为负数。
输入格式
共两行,每行包含一个整数。
输出格式
共一行,包含所求的差。
数据范围
1≤整数长度≤1051≤整数长度≤105
输入样例:
32
11
输出样例:
21
#include <iostream>
#include <vector>
using namespace std;
bool cmp(vector<int> &A, vector<int> &B)
{
if (A.size() != B.size()) return A.size() > B.size();
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 < A.size(); i ++ )
{
t = A[i] - t;
if (i < 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;
vector<int> A, B;
cin >> 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');
vector<int> C;
if (cmp(A, B)) C = sub(A, B);
else C = sub(B, A), cout << '-';
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
高精度除法
给定两个非负整数(不含前导 00) A,B,请你计算 A/B 的商和余数。
输入格式
共两行,第一行包含整数 A,第二行包含整数 B。
输出格式
共两行,第一行输出所求的商,第二行输出所求余数。
数据范围
1≤A的长度≤100000
1≤B≤10000
B一定不为 0
输入样例:
7
2
输出样例:
3
1
题解:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> div(vector<int> &A, int b, int &r)
{
vector<int> C;
r = 0;
for (int i = A.size() - 1; i >= 0; i -- )
{
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a;
vector<int> A;
int B;
cin >> a >> B;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
int r;
auto C = div(A, B, r);
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl << r << endl;
return 0;
}
高精*低精
#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 < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
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 -- ) printf("%d", C[i]);
return 0;
}
高精*高精
#include <iostream>
using namespace std;
int a[100010] , b[100010], c[2000010];
string x, y;
int main(){
cin >> x >> y;
int a1 = x.size();
int b1 = y.size();
int c1 = a1 + b1;
for(int i = 0; i < a1; i++){
a[a1 -i -1] = x[i] - '0';//输入第一个数
}
for(int i = 0; i < b1; i++){
b[b1 -i -1] = y[i] - '0';//输入第二个数
}
for(int i = 0; i < a1; i++){
for(int j = 0; j < b1; j++){
c[i + j] += a[i] * b[j];//按位相乘并累加
}
}
int x = 0;
for(int i = 0; i < c1; i++){//处理进位
c[i] += x;
x = c[i]/10;
c[i] %= 10;
}
int k = c1;//两数相乘后的位数不会超过两数位数相加之和,所以c1足够了
while(c[k]==0 && k > 0){//处理前缀0
k--;
}
for(int i = k; i >= 0; i--){//倒序输出
cout << c[i];
}
return 0;
}
二分
整数二分
二分的关键是区间更新问题的处理,其本质是边界的寻找,
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
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 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
mid=l+r+1>>1;的原因是避免死循环;
check(mid)一律写成q[mid]>=x(目标即“边界”)或q[mid]<=x
带上=是为了贴合模板,避免出现奇怪的问题因此统一一下。
如果数组内无目标值即为
q[l]!=x
l和r完全等价,x为目标值
浮点数二分
因为浮点型可以严格的除于2,所以浮点型二分不存在边界问题;
double a;
cin>>a;
double l=-10000,r=10000;
//while(fabs(r-l)>1e-8)
for(int i=0;i<100;i++)
{
double mid=(l+r)/2;
if(mid*mid*mid>=a)r=mid;
else l=mid;
}
l,r的值是数的边界由题中得,如上面的代码解决的问题数的范围是-10000到10000
double l=-10000,r=10000;
//while((r-l)>1e-8)
这两个本质是一样的,即精度达到一定程度;
经验:一般保留n位小数时l-r>1e-(n+2);
快速幂(欧拉降幂)
用到数论知识,存在大量边界问题,建议直接背模板;
/*typedef long long ll;
int qmi(int a, int k, int p)
{
int res = 1 ;
while (k)
{
if (k&1) res = (ll)res * a % p;
k >>= 1;
a=(ll)a*a%p;
}
return res;
}*/
int qmi(int m, int k, int p)
{
int res = 1 % p, t = m;
while (k)
{
if (k&1) res = (long long)res * t % p;
t = (long long)t * t % p;
k >>= 1;
}
return res;
}
KMP
寻找子串
// 注:这不是题目的AC代码,是一个最基本的模板代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 10010; //N为模式串长度,M匹配串长度
int n, m;
int ne[M]; //next[]数组,避免和头文件next冲突
char s[N], p[M]; //s为模式串, p为匹配串
int main()
{
cin >> n >> s+1 >> m >> p+1; //下标从1开始
//求next[]数组
for(int i = 2, j = 0; i <= m; i++)//ne[1]一定=0,从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 <= n; i++)
{
while(j && s[i] != p[j+1]) j = ne[j];
if(s[i] == p[j+1]) j++;
if(j == m) //满足匹配条件,打印开头下标, 从0开始
{
//匹配完成后的具体操作
//如:输出以0开始的匹配子串的首字母下标
//printf("%d ", i - m); (若从1开始,加1)
j = ne[j]; //因为匹配的子串可能不止一个,所以J指针回到ne[j]
}
}
return 0;
}
本模板中含有位运算相关知识
差分
类似于数学中的求导和积分,差分可以看成前缀和的逆运算。
差分数组:
首先给定一个原数组a:a[1], a[2], a[3],,,,,, a[n];
然后我们构造一个数组b : b[1] ,b[2] , b[3],,,,,, b[i];
使得 a[i] = b[1] + b[2 ]+ b[3] +,,,,,, + b[i]
也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组的差分数组。
考虑如何构造差分b数组?
最为直接的方法
如下:
a[0 ]= 0;
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];
b[3] =a [3] - a[2];
........
b[n] = a[n] - a[n-1]
for(int i=1;i<=n;i++)
{
cin>>a[i];
b[i]=a[i]-a[i-1]//a[]定义为全局变量,a[0]=0;
}
int l,r,c;
b[l]+=c;
b[r+1]-=c;
for(int i=1;i<=n;i++)
{
a[i]=b[i]+a[i-1];
}
位运算
在二进制下操作
1.右移运算符与&
n的k位是几(二进制下)
n>>k&1;
个位是第零位,十位是第一位;
应用:10进制下的2进制
eg:(10)10=(1010)2;
for(int i=31;i>=0;i--)
{
cout<<(n>>k&1);
}
二进制是32位
2.lowbit
lowbit用于返回二进制下的最后一个1;
lowbit的内部实现
int lowbit(int x)
{
return x&-x;
}
应用:统计二进制下1的个数
while (x)x-=lowbit,cnt++;
DP
背包问题
1. 0 1背包
「0-1 背包」是较为简单的动态规划问题,也是其余背包问题的基础。
动态规划是不断决策求最优解的过程,「0-1 背包」即是不断对第 i个物品的做出决策,「0-1」正好代表不选与选两种决定
特点:每个物品仅能使用一次或0次
重要变量&公式解释
f[i][j]:表示所有选法集合中,只从前i个物品中选,并且总体积≤j的选法的集合,它的值是这个集合中每一个选法的最大值.
状态转移方程
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
f[i-1][j]:不选第i个物品的集合中的最大值
f[i-1][j-v[i]]+w[i]:选第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.
问题
集合如何划分
一般原则:不重不漏,不重不一定都要满足(一般求个数时要满足)
如何将现有的集合划分为更小的子集,使得所有子集都可以计算出来.
题目介绍
有 N件物品和一个容量为 V的背包,每件物品有各自的价值且只能被选择一次,要求在有限的背包容量下,装入的物品总价值最大。
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N][N];
int v[N],w[N];
int main(){
int 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=1;j<=m;j++)
{
if(j<v[i])
f[i][j]=f[i-1][j];
else
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
}
cout<<f[n][m];
return 0;
}
f[n][m]表示在体积不超过m的前提下从前n件物品中选的最优解,
其值的来源既是:f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
关键在+w[i]上;
通过穷举遍历所有情况,符合要求时更新f[n][m]的值,其本质是一种暴力
线性筛质数
暴力
#include<bits/stdc++.h>
using namespace std;
bool isprime(int a);
int main() {
int b;
cin>>b;
if(isprime(b)==1)cout<<"yes";
}
bool isprime(int a){
if(a<=1)return 0;
for(int i=2;i*i<=a;i++){
if(a%i==0)return 0;
}
return 1;
}
埃氏筛
#include<iostream>
using namespace std;
cnst int N=1e8+10;
int f[N];
int main(){
// f[i]=1代表i不是素数,f[i]=0代表i是素数
f[0]=1;
f[1]=1;
// 0 1是素数
int n;
cin>>n;
// 埃氏筛
for(int i=2;i<=n;i++){
if(f[i]==1)continue;
for(int j=2;i*j<=n;j++){
f[i*j]=1;
}
}
// 打印
for(int i=0;i<=n;i++){
if(f[i]==0){
cout<<i<<' ';
}
}
}
欧拉筛
时间复杂度最低且输出第K大的数有独到的优势;
#include <bits/stdc++.h>
using namespace std;
const int N=1e8+10;
int p[N];
int f[N];
int main()
{
int d=0;
f[0]=1;
f[1]=1;
int n;
cin>>n;
for(int i=2;i<=n;i++){
if(f[i]==0){//如果没被标记过,那么i是质数
p[d++]=i;
}
for(int j=0;j<d;j++){
if(p[j]*i<=n){//标记以i为最大因数的数为不是素数(除了1和本身)
f[p[j]*i]=1;
}else{
break;
}
if(i%p[j]==0){//如果p[j]是i的因数,那么后面的数都不是以i为最大因数的
break;
}
}
}
int q;
cin>>q;
while(q--)
{
int t;
cin>>t;
cout<<p[t-1]<<endl;
}
return 0;
}
搜索
DFS
深度优先算法,即DFS又称暴搜
关键:回溯,剪枝;
全排列问题
给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int n , path[N];
bool st[N]; // 状态数组
void dfs(int u ) // 第几个数字,一共几个数字
{
if(u == n)// 递归到最后一个数字
{
for (int i = 0; i < n; i ++ ) cout << path[i] << ' '; // 输出保存的结果
puts(" ");
}
for (int i = 1; i <= n; i ++ )
if (!st[i]) // 没有被用过的数
{
path[u] = i ;
st[i] = true; // i被用过
dfs(u + 1);// 走到下一层
st[i] = false;// 恢复现场(所谓回溯)
}
}
int main()
{
cin >> n;
dfs(0);
return 0;
皇后问题
n−皇后问题是指将 n个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数 n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数 n。
输出格式
每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。
其中 .
表示某一个位置的方格状态为空,Q
表示某一个位置的方格上摆着皇后。
每个方案输出完成后,输出一个空行。
注意:行末不能有多余空格。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
1≤n≤9
输入样例:
4
输出样例:
.Q..
...Q
Q...
..Q.
..Q.
Q...
...Q
.Q..
题解
#include <iostream>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool col[N], dg[N ], udg[N ];
void dfs(int u)
{
if (u == n)
{
for (int i = 0; i < n; i ++ )
{
for(int j=0;j<n;j++)
{
cout<<g[i][j];
}cout<<endl;
}cout<<endl;
return;
}//等价变形
/* if (u == n)
{
for (int i = 0; i < n; i ++ ) puts(g[i]);
puts("");
return;
}*/
for (int i = 0; i < n; i ++ )
if (!col[i] && !dg[u + i] && !udg[n + u - i])
{
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n + u - i] = true;
dfs(u + 1);
col[i] = dg[u + i] = udg[n + u - i] = false;
g[u][i] = '.';
}//und[n+u-i]等价与und[n+i-u]加n是加入偏移量,表示第i条(反)对角线;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
g[i][j] = '.';
dfs(0);
return 0;
}
bfs
迷宫问题
给定一个 n×m的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m)处,至少需要移动多少次。
数据保证 (1,1)处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n和 m。
接下来 n 行,每行包含 m个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8
题解:
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
typedef pair<int, int> PII;
int n, m;
int g[N][N], d[N][N];
int bfs()
{
queue< pair<int, int> > q;
q.push({0, 0});//起点
memset(d, -1, sizeof(d));//c++函数,将d中元素全部初始化为“-1”
d[0][0] = 0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//对应移动的方向
while (q.size())//队列不为空
{
PII t = q.front();//取队头元素,当前所在的位置
q.pop();//出队
for (int i = 0; i < 4; i++)
{
int x = t.first + dx[i], y = t.second + dy[i];//移动
if (x >= 0 && x < n && y >= 0 && y < m/*在范围内*/ && g[x][y] == 0/*0表示路,可走*/ && d[x][y] == -1/*该点第一次走*/)
{
d[x][y] = d[t.first][t.second] + 1;
/*这句话表示从起点走到(x, y)
的步数等于从起点走到(t.first, t.second)的步数加一,
因为我们是从(t.first, t.second)都到(x, y)的。*/
q.push({x, y});//将新坐标入队,更新起点
}
}
}
return d[n - 1][m -1];//返回(0,0)到终点的最短距离;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
STL
set(集合)
set,顾名思义,就是数学上的集合——每个元素最多只出现一次,并且set中的元素已经从小到大排好序。
常用函数
begin() 返回set容器的第一个元素的地址
end() 返回set容器的最后一个元素地址
clear() 删除set容器中的所有的元素
empty() 判断set容器是否为空
max_size() 返回set容器可能包含的元素最大个数
size() 返回当前set容器中的元素个数
erase(it) 删除迭代器指针it处元素
set的遍历
需要用到迭代器,具体的方法见下面的代码:
1 ##include <iostream>
2 #include<set>
3 using namespace std;
4
5
6 int main()
7 {
8 set<int> s; //创建一个int类型的set
9
10 s.insert(10); //插入数据
11 s.insert(30);
12 s.insert(20);
13 s.insert(40);
14
15 //遍历数据,用迭代器遍历数据
16 for (set<int>::iterator it = s.begin(); it != s.end(); ++it)
17 {
18 cout << *it << endl;
19 }
20 //这里用到了set中的元素已经从小到大排好序的性质
21
22 return 0;
23 }
最后,不得不提的就是结构体类型(struct )的set ,使用时必须要重载 '<' 运算符
1 #include<iostream>
2 #include<set>
3 #include<string>
4 using namespace std;
5 struct Info
6 {
7 string name;
8 double score;
9 bool operator < (const Info &a) const // 重载“<”操作符,自定义排序规则
10 {
11 //按score由大到小排序。如果要由小到大排序,使用“>”即可。
12 return a.score < score;
13 }
14 };
15 int main()
16 {
17 set<Info> s;
18 Info info;
19
20 //插入三个元素
21 info.name = "Jack";
22 info.score = 80;
23 s.insert(info);
24 info.name = "Tom";
25 info.score = 99;
26 s.insert(info);
27 info.name = "Steaven";
28 info.score = 60;
29 s.insert(info);
30
31 set<Info>::iterator it;
32 for(it = s.begin(); it != s.end(); it++)
33 cout << (*it).name << " : " << (*it).score << endl;
34 return 0;
35 }
数据结构
单链表(用数组模拟链表)
主要原因是速度快
题目描述
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 k个插入的数后面的数;
- 在第 k个插入的数后插入一个数。
现在要对该链表进行 M次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第 k 个插入的数并不是指当前链表的第 k个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这 n 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n个插入的数。
第一行包含整数 M,表示操作次数。
接下来 M行,每行包含一个操作命令,操作命令可能为以下几种:
H x
,表示向链表头插入一个数 xD k
,表示删除第 k 个插入的数后面的数(当 k 为 0时,表示删除头结点)。I k x
,表示在第 k个插入的数后面插入一个数 x(此操作中 k 均大于 0)。
共一行,将整个链表从头到尾输出。
1≤M≤1000001
所有操作保证合法。
输入样例:
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 <bits/stdc++.h>
using namespace std;
const int N = 100010;
// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 将x插到头结点(最常用)
void add_to_head(int x)
{
e[idx] = x, ne[idx] = head, head = idx ++ ;
}
// 将x插到下标是k的点后面
void add(int k, int x)
{
e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
}
// 将下标是k的点后面的点删掉
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m;
cin >> m;
init();
while (m -- )
{
int k, x;
char op;
cin >> op;
if (op == 'H')
{
cin >> x;
add_to_head(x);
}
else if (op == 'D')
{
cin >> k;
if (!k) head = ne[head];//ne[]模拟的是指针,ne[1]指向的是e[2],所以head = ne[head]表示的意义是指针移动一位
else remove(k - 1);
}
else
{
cin >> k >> x;
add(k - 1, x);
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
ne[]模拟的是指针,ne[1]指向的是e[2],所以head = ne[head]表示的意义是指针移动一位
循环中的i=ne[i]同理
双链表
题目描述
实现一个双链表,双链表初始为空,支持 55 种操作:
- 在最左侧插入一个数;
- 在最右侧插入一个数;
- 将第 k个插入的数删除;
- 在第 k个插入的数左侧插入一个数;
- 在第 k个插入的数右侧插入一个数
现在要对该链表进行 M次操作,进行完所有操作后,从左到右输出整个链表。
注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n个插入的数。
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
L x
,表示在链表的最左端插入数 x。R x
,表示在链表的最右端插入数 x。D k
,表示将第 k 个插入的数删除。IL k x
,表示在第 k 个插入的数左侧插入一个数。IR k x
,表示在第 k 个插入的数右侧插入一个数。
共一行,将整个链表从左到右输出。
1≤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 = 100010;
int m;
int e[N], l[N], r[N], idx;
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
//在k的左边插入x
add(l[k],x);
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
int main()
{
cin >> m;
// 0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
while (m -- )
{
string op;
cin >> op;
int k, x;
if (op == "L")
{
cin >> x;
insert(0, x);
}
else if (op == "R")
{
cin >> x;
insert(l[1], x);
}
else if (op == "D")
{
cin >> k;
remove(k + 1);
}
else if (op == "IL")
{
cin >> k >> x;
insert(l[k + 1], x);
}
else
{
cin >> k >> x;
insert(k + 1, x);
}
}
for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
栈(数组模拟)
栈的概念:先进后出,想象成向有底的试管中放与拿
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空,如果 tt > 0,则表示不为空
if (tt > 0)
{
}
单调栈的应用
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1−1。
第一行包含整数 N,表示数列长度。
第二行包含 N个整数,表示整数数列。
共一行,包含 N个整数,其中第 i个数表示第 i个数的左边第一个比它小的数,如果不存在则输出 −1−1。
1≤N≤105
1≤数列中元素≤109
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;//tt作用为指针
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
while (tt && stk[tt] >= x) tt -- ;//当栈不为空且栈头不符合需求时删除栈首
if (!tt) printf("-1 ");//当栈清空且未找到需要的值时cout -1;
else printf("%d ", stk[tt]);
stk[ ++ tt] = x;//将新值插入栈首
}
return 0;
}
队列(数组模拟)
队列的概念:先进先出,想象成向没有底的玻璃管中放与拿,只能上头进,下头出
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空,如果 hh <= tt,则表示不为空
if (hh <= tt)
{
}
单调队列的经典应用:滑动窗口
给定一个大小为 n≤106 的数组。
有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7]
,k为 3。
窗口位置 | 最小值 | 最大值 |
---|---|---|
[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 |
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
输入格式
输入包含两行。
第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。
第二行有 n 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
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];//q[]存放得时下标因此a[q[i]]表示的才时具体的值
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
q[ ++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");//换行
hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
q[ ++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
return 0;
}