算法 学习

写在前面:所有模板均来自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 }

数据结构

单链表(用数组模拟链表)

主要原因是速度快

题目描述

实现一个单链表,链表初始为空,支持三种操作:

  1. 向链表头插入一个数;
  2. 删除第 k个插入的数后面的数;
  3. 在第 k个插入的数后插入一个数。

现在要对该链表进行 M次操作,进行完所有操作后,从头到尾输出整个链表。

注意:题目中第 k 个插入的数并不是指当前链表的第 k个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这 n 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n个插入的数。

第一行包含整数 M,表示操作次数。

接下来 M行,每行包含一个操作命令,操作命令可能为以下几种:

  1. H x,表示向链表头插入一个数 x
  2. D k,表示删除第 k 个插入的数后面的数(当 k 为 0时,表示删除头结点)。
  3. 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 种操作:

  1. 在最左侧插入一个数;
  2. 在最右侧插入一个数;
  3. 将第 k个插入的数删除;
  4. 在第 k个插入的数左侧插入一个数;
  5. 在第 k个插入的数右侧插入一个数

现在要对该链表进行 M次操作,进行完所有操作后,从左到右输出整个链表。

注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 11 个插入的数,第 22 个插入的数,…第 n个插入的数。

第一行包含整数 M,表示操作次数。

接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. L x,表示在链表的最左端插入数 x。
  2. R x,表示在链表的最右端插入数 x。
  3. D k,表示将第 k 个插入的数删除。
  4. IL k x,表示在第 k 个插入的数左侧插入一个数。
  5. 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-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式

输入包含两行。

第一行包含两个整数 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;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值