acwing算法基础课整理ACM模板

ACM模板


目录

基础算法

排序

快速排序

#include<iostream>
using namespace std;

const int N = 1e6 + 10;
int q[N];
int n;

void quick_sort(int q[], int l, int r) {
    if(l >= r) return;
    int i = l-1, j = r+1, x = q[(l+r)>>1]; // 这里x选取的时候不要使用q[l],不然容易超时
    while(i < j) {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
    
        if(i < j)  swap(q[i], q[j]); // 如果i < j 则交换
    }
    quick_sort(q, l, j);
    quick_sort(q, j+1, r);
    
}

int main() {

    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;
}

归并排序

#include<iostream>
using namespace std;
const int N = 1e5+10;
int q[N], tmp[N];
int 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 i = l, j = mid+1, 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(int i = 0, j = l;  j <= r; i++, j++) q[j] = tmp[i];
}

int main() 
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);
    merge_sort(q, 0, n-1);
    for(int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}

统计逆序对数

#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int q[N], tmp[N];
int cnt = 0;
int n;

LL merge_sort(int q[], int l, int r){
    if(l >= r) return 0; // 只有一个元素,不必排序,直接返回
    
    int mid = l + r >> 1 ;
    LL res = merge_sort(q, l, mid);
    res += merge_sort(q, mid + 1, r);
    
    // 合并左右两段
    int i = l, j = mid+1, 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 ++], res += mid - i + 1; // 从i->mid 都大于a[j]
    }
    while(i <= mid) tmp[k ++] = q[i ++], cnt++;
    while(j <= r)   tmp[k ++] = q[j ++];
    
    // 复制回原数组
    for(int i = 0, j = l;  j <= r; i++, j++) q[j] = tmp[i];
    return res;
}

int main() 
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);
    LL res = merge_sort(q, 0, n-1);
    printf("%lld\n", res);
    return 0;
}

冒泡排序

#include<iostream> //包含输入输出头文件
#include<cmath>
using namespace std; //指定名字空间
int main() 
{ //主函数
	int a[100]; //定义数组,大小100
	int N; //元素的实际个数
	int i = 0, j = 0; //循环变量,并进行初始化
	cin >> N; //输入元素个数
			  //-------输入数据-----------
	for (i = 0; i<N; i++) //输入N个元素
		cin >> a[i]; //循环体只有一行
					 //-------排序---------------
	for (i = 0; i<N - 1; i++) { //控制n-1趟冒泡
		for (j = 0; j<N - 1 - i; j++)
		{
			if (a[j]>a[j + 1]) { //比较相邻的两个元素
				int tmp; //临时变量
				tmp = a[j]; //交换
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}
	//--------输出----------
	for (i = 0; i<N; i++) 
	{ //使用循环,输出N个元素
		cout << a[i] << " "; //输出a[i], 后加空格,不换行
	}
	cout << endl; //所有元素输出完之后才换行
	return 0; //函数返回
}

整数二分

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 n 和 q,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式

共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1
#include<iostream>
using namespace std;

const int N = 100010;

int a[N];
int n, q, k;

int main()
{
    scanf("%d%d", &n, &q);
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    while(q--){
        scanf("%d", &k);
        
        // 查找左边界
        int l = 0, r = n - 1;
        
        while(l < r) {
            int mid = l + r >> 1;
            if(a[mid] >= k) r = mid;
            else            l = mid + 1;
        }
        // 如果左边界不是k,说明数组没有k,直接输出-1 -1
        if(a[l] != k) cout<< "-1 -1" << endl;
        else {
            cout << l << " ";
            
            // 查找右边界
            int l = 0, r = n - 1;
            
            while(l < r) {
                int mid = l + r + 1>> 1;
                if(a[mid] <= k)  l = mid; // 因为这里是l = mid, 上面的mid 要变成  l+r+1 >> 1
                else             r = mid - 1;
            }
            cout << l << endl;
        }
    }
    return 0;
}

浮点数二分

给定一个浮点数 n,求它的三次方根。

输入格式

共一行,包含一个浮点数 n。

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 位小数。

数据范围

−10000≤n≤10000

输入样例:

1000.00

输出样例:

10.000000
#include<iostream>

using namespace std;
const double eps = 1e-8;   // eps 表示精度,取决于题目对精度的要求, 一般比所求精度高 2
double get(double a) {
    return a*a*a;
}

int main()
{
    double n, a;
    double l = -10000.0, r = 10000.0;
    double mid;
    scanf("%lf", &n);
    
    while(r - l > eps) {
        mid = (r + l) / 2;
        if(get(mid) >= n) r = mid;
        else              l = mid;
    }
    printf("%.6lf", r);
    return 0;
}

一维前缀和

输入一个长度为 n 的整数序列。

接下来再输入 m 个询问,每个询问输入一对 l,r。

对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。

#include<iostream>
using namespace std;
const int N = 100010;
int a[N], s[N];
int n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]), s[i] = s[i-1] + a[i];
    while(m --)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", s[r] - s[l-1]);
    }
    return 0;
}

二维前缀和

输入一个 n 行 m 列的整数矩阵,再输入 qq 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。

对于每个询问输出子矩阵中所有数的和。

输入格式

第一行包含三个整数 n,m,q。

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。

输出格式

共 q 行,每行输出一个询问的结果。

输入样例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:

17
27
21
S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1010;
int s[N][N];
int a[N][N];
int n, m, q;

int main()
{
    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++)
            s[i][j] = s[i-1][j] + s[i][j-1] - s[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", s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]);
    }    
    return 0;
}
image-20220321160704265

一维差分

输入一个长度为 n 的整数序列。

接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。

请你输出进行完所有操作后的序列

  • 首先构造差分数组
  • 然后再对区间加减某个数
  • 最后求前缀和得到每个数是多少
#include<iostream>
using namespace std;

const int N = 100010;

int n, m;
int a[N], b[N];
int l, r, c;

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r+1] -= c;
}

int main() 
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    //-----------------------------
    for(int i = 1; i <= n; i++) b[i] = a[i] - a[i-1]; // 构造差分数组!!!
    //-----------------------------
    while(m--) {  //
        scanf("%d%d%d", &l, &r, &c);
        insert(l, r, c);
    }
    //------------------------------
    for(int i = 1; i <= n; i++) b[i] += b[i-1];
    
    for(int i = 1; i <= n; i++) printf("%d ", b[i]);
    
    return 0;
}

二维差分

输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素的值加上 c。

请你将进行完所有操作后的矩阵输出。

输入格式

第一行包含整数 n,m,q。

接下来 n 行,每行包含 m 个整数,表示整数矩阵。

接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。

输出格式

共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

输入样例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:

2 3 4 1
4 3 4 1
2 2 2 2
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c

我们可以先假想a数组为空,那么b数组一开始也为空,但是实际上a数组并不为空,因此我们每次让以(i,j)为左上角到以(i,j)为右下角面积内元素(其实就是一个小方格的面积)去插入 c=a[i][j],等价于原数组a中(i,j) 到(i,j)范围内 加上了a[i][j],因此执行n*m次插入操作,就成功构建了差分b数组.

#include <iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N], b[N][N];

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()
{
    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 ++)
            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]);
        puts("");
    }
    
    return 0;
}

双指针算法

给定一个长度为 nn 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数 nn。

第二行包含 nn 个整数(均在 0∼10^5 范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

输入样例:

5
1 2 2 3 5

输出样例:

3
#include<iostream>
using namespace std;

const int N = 100010;
// 第一行包含整数n。
// 第二行包含n个整数(均在0~100000范围内),表示整数序列。
int n;
int a[N], s[N]; // s[i] 表示 i出现的次数,初始为0 

int main()
{
	int res = 0;
	
	scanf("%d", &n);
	for(int i = 0; i < n; i++) scanf("%d", &a[i]);

	 
	for(int i = 0, j = 0; i < n; i++) {
		
		s[a[i]]++; // a[i]出现次数+1
		
		while(j <= i && s[a[i]] > 1) { // a[i] > 1说明 [j,i]区间出现了重复元素,此时j指针需要向i方向移动,每次移动,就将a[j]在s中个数-1 
									   // j <= i可以去掉,因为上面有s[a[i]]++,到j=i的时候一定有s[a[i]] == 1, 不满足s[a[i]]>1 会break 
			s[a[j]]--; // a[j]出现次数-1 
			j++;
		} 
		
		res = max(res, i - j + 1);
	} 
	
	cout << res;
	
	return 0;
}

位运算

lowbit(x) = x&(-x)

#include<iostream>
#include<cstdio>
using namespace std;

int lowbit(int x) //使用lowbit操作,每次lowbit操作截取一个数字最后一个1后面的所有位
{
    return x & (-x);
}

int main()
{
    int n;
    cin >> n;
    while(n --)
    {
        int x;
        int res = 0;
        cin >> x;
        while(x) x -= lowbit(x), res++; //,每次减去lowbit得到的数字,直到数字减到0,就得到了最终1的个数,
        cout << res << " ";
    }
    return 0;
}

离散化

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

区间合并

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            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;
}

高精度

高精度加法

#include <iostream>
#include <vector>

using namespace std;

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);
    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');

    auto C = add(A, B);

    for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
    cout << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/39792/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

高精度减法

#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;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/39793/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

高精度乘低精度

#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;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/39794/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

高精度除以低精度

#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;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/39795/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

高精度阶乘

#include <iostream>
#include <vector>
using namespace std;

const int N = 1010;
// 存储1-1000的阶乘
vector<int> F[N];

// 高精度乘法
vector<int> mul(vector<int> &A, int b)
{
    int t =0;
    vector<int> C;
    for(int i=0;i<A.size() || t;i++)
    {
        if(i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    A = C; // 更新A,没有这个会得不到正确答案
    return C;
}



int main()
{
    int a;
    F[0] = {1};
    vector<int> A{1};
    // 计算阶层并存储
    for(int i=1;i<=1000;i++)
    {
        F[i] = mul(A,i);
    }
    while(cin >> a)
    {
        // 查表输出
        for(int i=F[a].size()-1;i>=0;i--) cout << F[a][i];
        cout << endl;
    }
}

链接:https://www.acwing.com/solution/content/89358/
来源:AcWing

高精度进制转换

将 M 进制的数 X 转换为 N 进制的数输出。

输入格式

第一行包括两个整数:M 和 N。

第二行包含一个数 X,X 是 M 进制的数,现在要求你将 M 进制的数 X 转换成 N 进制的数输出。

输出格式

共一行,输出 X 的 N 进制表示。

数据范围

2≤N,M≤36,
X 最多包含 100位。
在输入中,当某一位数字的值大于 10(十进制下)时,我们用大写字母 A∼Z,分别表示(十进制下的)数值 10∼35。
在输出中,当某一位数字的值大于 10(十进制下)时,我们用小写字母 a∼z,分别表示(十进制下的)数值 10∼35。

输入样例:

10 2
11

输出样例:

1011
#include <bits/stdc++.h>

using namespace std;


string conversion(string s, int m, int n)
{
	vector<int> a;
    for (int i = s.size() - 1; i >= 0; i--)
    {
        char c = s[i];
        if (c >= 'A') a.push_back(c - 'A' + 10);
        else a.push_back(c - '0');
    }
    string res;
    if (s == "0") res = "0";
    else {
        while (a.size())
        {
            int r = 0;
            for (int i = a.size() - 1; i >= 0; i--)
            {
                a[i] += r * m;
                r = a[i] % n;
                a[i] /= n;
            }
            while (a.size() && a.back() == 0) a.pop_back();
            if (r < 10) res += to_string(r);
            else res += r - 10 + 'a';
        }
        reverse(res.begin(), res.end()); // 这一步重要
    }
    return res;
}

int main()
{
	int m, n;
	string s;
	cin >> m >> n >> s;
	cout << conversion(s, m, n) << endl;
	return 0;
}

Java 大整数

import java.util.Scanner;
import java.math.BigInteger;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        BigInteger a,b;
            a=input.nextBigInteger();
            b=input.nextBigInteger();
            System.out.println(a.add(b));
        }
    }

import java.util.*;
import java.math.BigInteger;
public class Main {
    public static void main(String args[]) {
        Scanner input = new Scanner(System.in);
        BigInteger num1 = input.nextBigInteger();
        BigInteger num2 = input.nextBigInteger();
        System.out.println(num1.subtract(num2));
    }
}

import java.util.* ;
import java.math.BigInteger ;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in) ;
        BigInteger m,n;
        m=input.nextBigInteger();
        n=input.nextBigInteger();
            m = m.multiply(n) ;
        System.out.println(m);
    }
}

import java.util.*;
import java.math.BigInteger;
public class Main {
    public static void main(String args[]) {
        Scanner input = new Scanner(System.in);
        BigInteger num1 = input.nextBigInteger();
        BigInteger num2 = input.nextBigInteger();
        System.out.println(num1.divide(num2));//相除
        System.out.println(num1.remainder(num2));//余数
    }
}

64位整数乘

求 a 乘 b 对 p 取模的值。

输入格式

第一行输入整数a,第二行输入整数b,第三行输入整数p。

输出格式

输出一个整数,表示a*b mod p的值。

数据范围

1≤a,b,p≤10^18

输入样例:

3
4
5

输出样例:

2

a*10 = a*(1011)

和快速幂的思想类似,只不过把里面的乘换成了加

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;

LL qadd(LL a, LL b, LL p)
{
	LL res = 0;
	LL t = a;
	while(b)
	{
		if(b & 1) res = (res + t) % p;
		t = (t + t) % p;
		b >>= 1;
	}
	return res;
 } 
 
int main()
{
	LL a, b, p;
	scanf("%lld%lld%lld", &a, &b, &p);
	printf("%lld\n", qadd(a, b, p));
	return 0;
}

数据结构

单链表

const int N = 100010, M = N * 2;
int h[N], e[M], w[M], ne[M], idx = 0;
void init()
{
    memset(h, -1, sizeof h);
}
void add(int a, int b, int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}

并查集|带size

  1. 给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

    现在要进行 m 个操作,操作共有三种:

    1. C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
    2. Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
    3. Q2 a,询问点 a 所在连通块中点的数量
#include<iostream>
#include<cstdio>
using namespace std;

const int N = 100010;

int p[N], cnt[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) p[i] = i, cnt[i] = 1;

    while (m -- )
    {
        string op;
        cin >> op;
        if(op == "C")
        {
            int a, b;
            cin >> a >> b;
            int fa = find(a), fb = find(b);
            if(fa != fb)
            {
                p[fa] = fb;
                cnt[fb] += cnt[fa];
            }
        }
        else if(op == "Q1")
        {
            int a, b;
            cin >> a >> b;
            int fa = find(a), fb = find(b);
            if(fa == fb) cout << "Yes" << endl;
            else         cout << "No" << endl;
        }
        else
        {
            int a;
            cin >> a;
            cout << cnt[find(a)] << endl;
        }
    }

    return 0;
}

Trie树

维护一个字符串集合,支持两种操作:

“I x”向集合中插入一个字符串x;
“Q x”询问一个字符串在集合中出现了多少次。
共有N个操作,输入的字符串总长度不超过 10^5,字符串仅包含小写英文字母。

输入格式

//trie树算法
#include<iostream>
#include<cstdio>
#include<cstring>
const int N = 1000010;

using namespace std;
int son[N][26], cnt[N], idx;
// son[][]存储树中每个节点的子节点,
// cnt[i]表示该单词i的出现次数
// idx表现当前用到了哪个节点,idx单调递增

void insert(char * str)
{
	int p = 0; // parent
	for(int i = 0; str[i]; i++)
	{
		int u = str[i] - 'a';
		if(!son[p][u]) son[p][u] = ++idx; // idx = 0 是根节点root 
		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 n;
	scanf("%d", &n);
	char op[2];
	char str[N];
	while(n--)
	{
		scanf("%s%s",op,str);
		if(op[0] == 'I') insert(str);
		else 			 printf("%d\n", query(str));
	}	
	return 0;
} 

KMP

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1010; //N为模式串长度,M匹配串长度
int n, m, q;
// char s[N], p[M];  //s为模式串, p为匹配串
char s[N], t[N]; 
// 都存到 s+1, p+1里面去 
// 模式串p在源串s中出现的位置,m是s的长度,n是q长度 
int KMP(char s[], char q[], int m, int n)
{
	int res = 0;
	
	int ne[N]; //next[]数组,避免和头文件next冲突
	
	for(int i=2,j=0;i<=n;i++)
    //j表示匹配成功的长度,i表示q数组中的下标,因为q数组的下标是从1开始的,只有1个时,一定为0,所以i从2开始
    {
        while(j&&q[i]!=q[j+1]) j=ne[j];
        //如果不行可以换到next数组
        if(q[i]==q[j+1]) j++;
        //成功了就加1
        ne[i]=j;
        //对应其下标
    }
    //j表示匹配成功的长度,因为刚开始还未开始匹配,所以长度为0
    for(int i=1,j=0;i<=m;i++)
    {
        while(j&&s[i]!=q[j+1]) j=ne[j];
        //如果匹配不成功,则换到j对应的next数组中的值
        if(s[i]==q[j+1]) j++;
        //匹配成功了,那么j就加1,继续后面的匹配
        // 匹配成功!!
        if(j==n)//如果长度等于n了,说明已经完全匹配上去了
        {
        	res++; 
            //printf("%d ",i-j);/ 
            //因为题目中的下标从0开始,所以i-j不用+1;
            j=ne[j];
            //为了观察其后续是否还能跟S数组后面的数配对成功
        }
    }
    return res;
}
int main()
{
	cin >> n >> m >> q;
	
	cin >> s+1 >> t+1;
	
	while(q --)
	{
		int l, r;
		scanf("%d %d", &l, &r);
		char ts[N];
		for(int i = l, j = 1; i <= r; i++, j++) ts[j] = s[i];
		int res = KMP(ts, t, r-l+1, m); // 模式串s, 模板串q, s长度,q长度 
		printf("%d\n", res);
	}
	return 0;
} 

存图

邻接矩阵


邻接表vector

const int N = 100010;
int n, m;
vector<int> G[N]; // 存图
// 在顶点a和b之间建立一条边
// 头插法
void add(int a, int b)
{
   G[a].push_back(b);
}

int main()
{
    // 遍历一个点能到的其他点
    // a点能到的其他点
    for(auto p : G[a])
    {
        int t = p;
        // t就是与a邻接的点
    }
}

带权:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
#define N 10000
using namespace std;
struct EDGE
{
    int to;//终点
    int cost;//边的权值
};
vector<EDGE>G[N];//G[i]中i表示出发点

void add(int a, int b, int w)
{
    EDGE e;
    e.to = b;
    e.cost = w;
    G[a].push_back(e);
}

int m,n;
int temp1;//出发点
int main()
{
    scanf("%d%d",&n,&m);
    while(m--)
    {
        EDGE e;
        scanf("%d%d%d",&temp1,&e.to,&e.cost);//输入出发点,终点,边的权值
        G[temp1].push_back(e);//将数据压入动态数组,表示在这个出发点下引出的边
        //相当于二维动态数组
    }
    for (int i=1; i<=n; i++) //按照出发点的顺序遍历
    {
        for(int j=0; j<G[i].size(); j++) //遍历出发点所引出的边
        {
            EDGE e=G[i][j];//1以二维数组形式输出
            printf("from %d to %d,the cost is %d\n",i,e.to,e.cost);
        }
    }
    return 0;
}

数组模拟链表

const int N = 100010;
int h[N], e[N], ne[N], idx = 0; // n个单链表,用来存每个点能够到达的其他顶点

void init()
{
	memset(h, -1, sizeof h); // 初始所有表头指向-1
}
// 在顶点a和b之间建立一条边
// 头插法
void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}


int main()
{
    // 遍历一个点能到的其他点
    // a点能到的其他点
    for(int i = h[a]; i != -1; i = ne[i])
    {
        int t = e[i];
        // t就是与a邻接的点
    }
}

带权

const int N = 2010, M = 10010;

int h[N], w[M], e[M], ne[M], idx;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int main()
{
     for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
         	int cost = w[i];
        }
}

搜索

dfs

给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

  • 使用path[]保存每层选择哪个数
  • 使用st[]标记每个数是不是被上面的某层使用过
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 10; //

int path[N]; // 用来存每层选择的数是哪个
bool st[N]; // 用来表示一个数是否被用过 

int n;

// 当前是第u层 ,从第0层开始 
void dfs(int u)
{
	if(u == n) // 到达第n层,可以输出并返回了 
	{
		for(int i = 0; i < n; i++ )
		{
			printf("%d ", path[i]);
		 } 
		 puts("");
		 return;
	} 
	
	for(int i = 1; i <= n; i++)
	{
		if(!st[i]) // 这个数没用过 
		{
			path[u] = i; // 本层用i这个数
			st[i] = true; 
			dfs(u + 1); // 递归到下一层 
			st[i] = false; 	  // 恢复现场
		}	
	}
} 

int main()
{
	scanf("%d", &n);
	dfs(0);
	return 0;
 } 

dfs剪枝+顺序

小猫爬山

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。

经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

翰翰和达达只好花钱让它们坐索道下山。

索道上的缆车最大承重量为 W,而 NN 只小猫的重量分别是 C1、C2……CN。

当然,每辆缆车上的小猫的重量之和不能超过 W。

每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

输入格式

第 1 行:包含两个用空格隔开的整数,N 和 W。

第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 ii 只小猫的重量 Ci。

输出格式

输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围

1≤N≤181≤N≤18,
1≤Ci≤W≤1081≤Ci≤W≤108

输入样例:

5 1996
1
2
1994
12
29

输出样例:

2
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 30;
int n, Wmax;
int ans = 0x3f3f3f3f; // 记录最终结果 
int sum[N]; // sum[i]表示第i辆扯已经装载了多少重量
int w[N]; // 每个小猫的重量 
// 当前考虑第u只小猫,已经使用了k辆车 
void dfs(int u, int k)
{
	if(k >= ans) return; // 重要剪枝!!如果当前搜索到的答案已经比最小的大了,后面不用搜索了
	
	if(u >= n) // 已经装完了所有n只小猫 
	{
		ans = min(ans, k);
		return;
	}
	
	for(int i = 0; i < k; i++)
	{
		// 第i辆车可以装下小猫u 
		if(sum[i] + w[u] <= Wmax)
		{
			sum[i] += w[u];
			dfs(u+1, k);
			sum[i] -= w[u]; // 恢复现场 
		}
	}
	// 新开一辆车
	sum[k] += w[u]; 
	dfs(u+1, k+1);
	sum[k] -= w[u]; 
 } 
 
int main()
{
	scanf("%d%d", &n, &Wmax);
	for(int i = 0; i < n; i++) scanf("%d", &w[i]);
	
	sort(w, w+n);
	reverse(w, w+n); // 从大到小排序,剪枝【可有可无】
	
	dfs(0, 0); // 从第0只小猫开始,使用0辆车 
	
	printf("%d", ans);
	return 0;
}

题目描述

给定 n 个正整数,将它们分组,使得每组中任意两个数互质。

至少要分成多少个组?

输入格式

第一行是一个正整数 n。

第二行是 n 个不大于10000的正整数。

输出格式

一个正整数,即最少需要的组数。

数据范围

1≤n≤10

输入样例:

6
14 20 33 117 143 175

输出样例:

3
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}
bool isZ(int a, int b)
{
    if(gcd(a, b) > 1) return false;
    else              return true;
}
int ans = 0x3f3f3f3f;
vector<int> h[12];
int a[12];
int n;

void dfs(int u, int k)
{
    if(k >= ans) return; // 剪枝

    if(u >= n)
    {
        ans = min(ans, k);
        return;
    }

    for(int i = 0; i < k; i++)
    {
        vector<int> tmp = h[i]; // 第k组有的所有互质的数
        bool flag = true;
        for(int j = 0; j < tmp.size(); j++)
        {
            if(!isZ(a[u], tmp[j]))
            {
                flag = false;
                break;
            }
        }

        if(flag)
        {
            h[i].push_back(a[u]);
            dfs(u+1, k);
            h[i].pop_back(); // 恢复现场
        }
    }

    h[k].push_back(a[u]);
    dfs(u+1, k+1);
    h[k].pop_back();
}

int main()
{
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];

    dfs(0, 0);

    cout << ans;
    return 0;
}

记忆化dfs

给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。

矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。

一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。

当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。

下面给出一个矩阵作为例子:

1  2  3  4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

在给定矩阵中,一条可行的滑行轨迹为 24−17−2−1

在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−1,沿途共经过 25 个区域。

现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。

输入格式

第一行包含两个整数 R 和 C。

接下来 R 行,每行包含 C 个整数,表示完整的二维矩阵。

输出格式

输出一个整数,表示可完成的最长滑雪长度。

数据范围

1≤R,C≤300
0≤矩阵中整数≤10000

输入样例:

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

输出样例:

25
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring> 
using namespace std;
const int N = 310;
int n, m;
int f[N][N];
int g[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int dfs(int x, int y)
{
	int &v = f[x][y];
	if(v != -1) return v;
	
	// 遍历四个方向
	v = 1; // 这个很重要!!! 
	for(int i = 0; i < 4; i++)
	{
		int nx = x + dx[i], ny = y + dy[i];
		
		if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && g[x][y] > g[nx][ny])
		{
			v = max(v, dfs(nx, ny) + 1);
		}
		
	 } 
	
	return v;
 } 
 
 
 int main()
 {
 	scanf("%d%d", &n, &m);
 	for(int i = 1; i <= n; i++)
 		for(int j = 1; j <= m; j++)
 			scanf("%d", &g[i][j]);
 			
 			
 	memset(f, -1, sizeof f);
 	int res = 0;
 	for(int i = 1; i <= n; i++)
	 	for(int j = 1; j <= m; j++)
		{
		 	res = max(res, dfs(i, j));
//			printf("%d ", dfs(i, j));
		}
	cout << res << endl;
	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<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring> // memset
using namespace std;

typedef pair<int , int> PII;
const int N = 110, INF = 0x3f3f3f3f;

int g[N][N];
int d[N][N];
// bool st[N][N];
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1}; // 上下左右四个方向 

int n, m;

int bfs()
{
	queue<PII> q;
	q.push({0 ,0});
	
	
	memset(d, 0x3f, sizeof d);// 每个点初始距离为0x3f3f3f3f 更好
	
	d[0][0] = 0; //到起点的距离初始化为0
// 	st[0][0] = true;
	while(!q.empty())
	{
		PII t = q.front();
		q.pop();
		int x = t.first, y = t.second; 
		
		for(int i = 0; i < 4; i++)
		{
			int nx = x + dx[i], ny = y + dy[i];
            // 第一次发现该点并且该点可以走
			if(nx >= 0 && nx < n && ny >= 0 && ny < m && d[nx][ny] == INF && g[nx][ny] == 0)
			{
				// st[nx][ny] = true;
				q.push({nx, ny});
				d[nx][ny] = d[x][y] + 1;
			}
		}
	 } 
	return d[n-1][m-1];	
} 

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 0; i < n; i++)
		for(int j = 0; j < m; j++)
			scanf("%d", &g[i][j]);
	cout << bfs() << endl;
	
	return 0;
}

8数码

在一个 3×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3 的网格中。

例如:

1 2 3
x 4 6
7 5 8

在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x

例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。

输入格式

输入占一行,将 3×3 的初始网格描绘出来。

例如,如果初始网格如下所示:

1 2 3 
x 4 6 
7 5 8 

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个整数,表示最少交换次数。

如果不存在解决方案,则输出 −1。

输入样例:

2  3  4  1  5  x  7  6  8

输出样例

19

思路就是把每种状态看成一个节点,初始的状态距离为0,每切换一个状态,到下一个状态的距离就是当前状态距离+1,求到最后一个状态节点的距离

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<unordered_map>
#include<queue> 
#include<cstring> 
using namespace std; 



int bfs(string start)
{
	string end = "12345678x"; // 结束状态 
	
	unordered_map<string, int> d; // 用来存到某个状态的距离 
	d[start] = 0;
	queue<string> q;
	
	q.push(start);
	
	// 对每一个状态,进行bfs搜索 
	while(q.size()) 
	{
		string t = q.front();
		q.pop();
		int distance = d[t]; 
		
		if(t == end) return distance; //找到了最终状态,bfs第一次发现的就是最短的路径 
		
		int k = t.find('x');
		int ax = k / 3, ay = k % 3; // 得到x在start转成三维矩阵后的坐标
		// 上下左右四个方向
		int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
		for(int i = 0; i < 4; i++)
		{
			int nx = ax + dx[i], ny = ay + dy[i];
			if(nx >= 0 && nx < 3 && ny >= 0 && ny < 3)
			{
				swap(t[k], t[nx * 3 + ny]);
				if(!d.count(t))  //如果这个状态没有出现过 
				{
					d[t] = distance + 1;
					q.push(t);
				} 
				swap(t[k], t[nx * 3 + ny]);
			}
		} 
	}
	
	return -1;	 
}

int main()
{
	string s, start; 
	for(int i = 0; i < 9; i++)
	{
		cin >> s;
		start += s;
	}
	
	cout << bfs(start) << endl;
	return 0;
}


图论

拓扑排序

给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。

若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。

d[u]记录每个节点u的入度

维护一个队列q,把所有入度为0的点放入队列中

每次从队列中取一个点,遍历该点所有的邻接点,并将每个邻接点的入度-1,如果-1后该邻接点的入度也变成了0,那么将其加入队列

同时使用res[N]记录每个进入过队列中的点

如果队列为空时,res的大小为n说明存在拓扑排序,输出即可;否则说明存在环

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;

const int N = 100010, M = 2*N;

int n, m; 
int d[N];// 存每个节点的入度 
queue<int> q; //存所有入度为0的点 
int res[N], cnt = 0;
//存图
int h[N], e[M], ne[M], idx = 0;
void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

void tor()
{
	// 先把入度为0的点加入队中 
	for(int i = 1; i <= n; i++)
		if(!d[i])
		{
			q.push(i);
			res[cnt++] = i;		
		}
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		// t所有指向的点 
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			
			d[j]--; // 该点入度-1 
			if(!d[j]) 
			{
				q.push(j); // 若度为0,加入队列 
				res[cnt++] = j;	
			}
		}
	}
}

int main()
{
	memset(h, -1, sizeof h);
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
		d[b] ++;
	}
	
	tor();
	
	if(cnt < n) cout << -1 << endl;
	else {
		for(int i = 0; i < n; i++)
		cout << res[i] << " ";
	}
	puts("");
	
	return 0;
}

Dijkstra 最短路

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到n 号点,则输出 −1。

注意不能存在负边!

模板

注意初始化的时候,初始化为正无穷

循环n-1次,然后每次确定一个点的最短距离

朴素做法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 510;
int g[N][N]; // 用邻接矩阵存图
int st[N];// st[i] = true 表示i点的最短距离已经确定了
int dist[N]; // 存从1到每个点的最短距离
int n, m;
// 返回从1到n的最短距离 
int dijkstra()
{
	// 初始化从1到所有点距离为正无穷 
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;

	
	// 循环n-1次每次取未确定的点里面距离最小的 
	for(int i = 0; i < n-1; i++)
	{
		// 从没确定的点里面选一个最小的值 t 
		int t = -1;
		for(int i = 1; i <= n; i++) 
			if(!st[i] && (t == -1 || dist[i] < dist[t]))
				t = i;
		
		// 跟新t指向节点的最短距离 
		for(int i = 1; i <= n; i++)
			// dist[i] = min(dist[i], dist[t] + g[t][i]);
			if(!st[i] && dist[i] > dist[t] + g[t][i])
				dist[i] = dist[t] + g[t][i]; 
		
		
		st[t] = true; //确定了一个点的最短距离 
	}
	
	if(dist[n] == INF)        return -1;
	else					  return dist[n];
} 

int main()
{
	// 初始化所有点之间边权为无穷大 
	memset(g, 0x3f, sizeof g);
	 
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		g[a][b] = min(g[a][b], c); // 有重边的话选小的那个 
	}
	int t = dijkstra();
	
	printf("%d\n", t); 
	return 0;
}

堆优化做法

适用范围:所有边权为正

代码1:用vector建图

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring> 
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
const int INF = 0x3f3f3f3f;

int dist[N]; // 从1到每个点的最短距离 
bool st[N]; // 每个点是否出现过 
int n, m;

// 一个点和边权
struct VER
{
	int to;
	int w;	
}; 
vector<VER> h[N];
// a->b 边权是w 
void add(int a, int b, int w)
{
	VER ver;
	ver.to = b;
	ver.w = w;
	h[a].push_back(ver); 
} 
// 定义图 


int dijkstra()
{
	// 初始化所有点的最小距离为INF,1的最小距离为0 
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	
	priority_queue< PII, vector<PII>, greater<PII> > heap; //定义小根堆 
	heap.push({0,1}); // {distance, ver} push到堆里的是一个pair,第一个是到该点的最短距离,第二个是该点的编号,
				       // 因为优先队列按照第一个排序 
	// 每次不continue的时候确定一个点的最小距离 
	while(heap.size())
	{
		PII t = heap.top();
		heap.pop();
		int distance = t.first, ver = t.second;
		
		if(st[ver]) continue; // 因为一个点的距离会被放入堆多次,只需要取一次最小的就行
		
		 
		// 遍历ver指向的所有点j 
		for(int i = 0; i < h[ver].size(); i++)
		{
			int j = h[ver][i].to;
			int w = h[ver][i].w;
			// 如果j没被确定最小距离,并且可以更新的话
			// 就更新j的最短距离,同时加入堆	
			if(dist[j] > distance + w)
			{
				dist[j] = distance + w;
				heap.push({dist[j], j}); //dist[j]会被放入多次,会有冗余,只需要取最小的那个就行 
			}
		}
		st[ver] = true; // 确定了该点的最小距离 
	}
	if(dist[n] == INF) return -1;
	else 			   return dist[n];
}

int main()
{
	scanf("%d%d", &n, &m);
	while(m --)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		// 重边冗余存储,不会对dijkstra有影响 
		add(a, b, w); 
	}
	int t = dijkstra();
	printf("%d\n", t);
	return 0; 
}

代码2:用链表建图

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 1e6 + 10;

int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    cout << dijkstra() << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48493/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Bellma_ford

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible

注意:图中可能 存在负权回路

输入格式

第一行包含三个整数 n,m,k

接下来 m 行,每行包含三个整数 x,y,z表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible

数据范围

1≤n,k≤500
1≤m≤10000
任意边长的绝对值不超过 10000

输入样例:

3 3 1
1 2 1
2 3 1
1 3 3

输出样例:

3
  1. 初始化操作和dijkstra相同:1的最短距离初始化为0,其他点初试化为INF
  2. 因为限制了要走的步数,蓑衣用bellman_ford算法:迭代k次可以找到从1出发走k步到个点的最短距离
  3. 注意每轮迭代进行松弛操作的时候,更新最短距离使用的是上次的结果,防止更新每条边的时候有串联效应
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510;
const int M = 10010;
const int INF = 0x3f3f3f3f;
struct Edge
{
	int a, b, w; // a->b 的边权为w 
}edges[M];// 保存所有边

int n, m, k;
int dist[N]; //当前 从1到每个点的最短路 
int last[N]; // 上一次迭代从1到每个点的最短路

void bellman_ford()
{
	// 和dijkstra一样初始化
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	// 迭代k次 ,就是松弛k轮 
	for(int i = 0; i < k; i++) 
	{
		memcpy(last, dist, sizeof dist); // 保存上一次迭代的结果
		for(int j = 0; j < m; j++) // 对所有边进行松弛操作 
		{
			int a = edges[j].a, b = edges[j].b, w = edges[j].w;
			dist[b] = min(dist[b], last[a] + w);
		}			
	} 
	
	
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 0; i < m; i++)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		edges[i] = {a, b, w}; // edges[i].a = a;  edges[i].b = b;  edges[i].w = w; 
	}
	bellman_ford();
	
	if(dist[n] > INF / 2) printf("impossible"); //这里是因为存在负权边,有可能到不了n但是存在负边使得dist[n] < INF 
	else				  printf("%d\n", dist[n]);
	return 0; 
 } 

Spfa 判断负环

给定一个n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你判断图中是否存在负权回路。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

如果图中存在负权回路,则输出 Yes,否则输出 No

数据范围

1≤n≤2000
1≤m≤10000
图中涉及边长绝对值均不超过 10000

输入样例:

3 3
1 2 -1
2 3 4
3 1 -4

输出样例:

Yes

spfa就是队列优化的bellman_ford算法

使用spfa判断图中是否存在负环的话,有两种方法

  1. 判断一个点是不是已经进入队列了n次,由bellman_ford算法可以知道,如果不存在负环最多经过n次迭代就可以得到1到任何一个点的最短距离,一个点最多被更新n-1次
  2. 判断到当前点的最短路径长度是不是大于等于n了!如果是的话,就说明存在一条路径有n的长度,那么该路径有n+1个点,必有两个点相同,所以一定存在负环

需要注意的是判断整个图存不存在负环,并不是判断从1开始存不存在负环,所以一开始要把所有点加入到队列中

代码1:用数组模拟单链表建图

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 2010, M = 10010;

int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

bool spfa()
{
    queue<int> q;

    for (int i = 1; i <= n; i ++ )
    {
        st[i] = true;
        q.push(i);
    }

    while (q.size())
    {
        int t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;

                if (cnt[j] >= n) return true;
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);

    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    if (spfa()) puts("Yes");
    else puts("No");

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48499/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码2:用vector建图

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector> 
#include<queue>
using namespace std;
const int N = 2010;
const int INF = 0x3f3f3f3f;
int n, m;
bool st[N];
int dist[N];
int cnt[N];
struct VER
{
	int to;
	int w;
};
vector<VER> h[N];

void add(int a, int b, int w)
{
	VER ver;
	ver.to = b;
	ver.w  = w;
	h[a].push_back(ver);
}

bool spfa()
{
// 	memset(dist, 0x3f, sizeof dist);
// 	dist[1] = 0;
	
	queue<int> q;
	// 所有点都入队 
	for(int i = 1; i <= n; i++)
	{
		st[i] = true;
		q.push(i);
	}
	
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		st[t] = false;
		if(cnt[t] >= n) return true;
		
		for(int i = 0; i < h[t].size(); i++)
		{
			int j = h[t][i].to, w = h[t][i].w;
			
			if(dist[j] > dist[t] + w)
			{
				dist[j] = dist[t] + w;
				cnt[j] = cnt[t] + 1; // t->j路径长度+1 
				
				
				if(!st[j]) // j不在队列中 
				{
					q.push(j);
					st[j] = true;
					
				}	
			}	
		} 
	}
	
	return false;
}	

int main()
{
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		add(a, b, w);
	}
	bool t = spfa();
	
	if(t) puts("Yes");
	else  puts("No");
	
	return 0;
}

Floyd 最短路

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数

再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 impossible

数据保证图中不存在负权回路。

输入格式

第一行包含三个整数 n,m,k

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

接下来 k 行,每行包含两个整数 x,y表示询问点 x 到点 y 的最短距离。

输出格式

共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible

数据范围

1≤n≤200
1≤k≤n^2
1≤m≤20000,
图中涉及边长绝对值均不超过 10000

输入样例:

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出样例:

impossible
1
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210;
const int INF = 0x3f3f3f3f; 
// 用邻接矩阵存储
int d[N][N];
int n, m, k;
void floyd()
{
	for(int k = 1; k <= n; k++)
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= n; j++)
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	// 初试化边权 
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
		{
			if(i == j) d[i][j] = 0;
			else 	   d[i][j] = INF;
		}
	// 
	while(m--)
	{
		int x, y, w;
		scanf("%d%d%d",&x, &y, &w);
		d[x][y] = min(d[x][y], w); // 对于重边去最小的 
	}
	floyd();
	while(k--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		if(d[a][b] > INF / 2) printf("impossible\n");
		else 	        printf("%d\n",d[a][b]); 
	}
	return 0;
} 

Prim最小生成树—稠密图

图中可能存在重边和自环,边权可能为负数。

  1. dist[i] <- INF
  2. for(i)
    1. 找到集合外距离最近的点t
    2. 用t更新其他点到集合的距离
    3. st[t] = true

要点其实是:每次贪心找到一条最小生成树上的路径

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m; 
int g[N][N]; // 建图 
int dist[N]; // dist[i]表示i到集合的距离
bool st[N]; // 表示当前的点在不在集合内部 
int prim()
{
	memset(dist, 0x3f, sizeof dist); // 初始化所有点的距离到集合为INF 
	
	int res = 0;
	
	for(int i = 0; i < n; i++) // 循环n次,每次确定一个加入集合的点 
	{
		// 选择集合外一个到集合最近的点 
		int t = -1;
		for(int j = 1; j <= n; j++)
			if(!st[j] && (t == -1 || dist[j] < dist[t]))
				t = j;
		
		if(i && (dist[t] == INF)) return INF; // 如果不是第一轮,并且集合外离集合最近的点的距离是INF,说明这个图不连通,直接返回inf
		 
		if(i) res += dist[t]; // 不是第一论, 就把这条边加到最小生成树的路径里面
		st[t] = true; // 这个点加入集合
		
		for(int k = 1; k <= n; k++) dist[k] = min(dist[k], g[t][k]); // 用t这个点更新其他点到集合的距离(集合内的点不用更新)
		
		// 上面先计算最短路,再更新其他点的顺序不能变 
	}
	return res; 
}

int main()
{
	memset(g, 0x3f, sizeof g);
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[u][v] = g[v][u] = min(g[u][v], w); // 是无向图;有重边 
	}
	int t = prim();
	if(t == INF) printf("impossible");
	else 	     printf("%d", t);
	
	return 0;
}

Kruskal小生成树—稀疏图

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,EE 表示图中边的集合,n=|V|,m=|E|。

由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

prim使用邻接表,适用于稠密图(点的个数比较小的情况)

算法步骤

  • 将所有边按照权重排序
  • 从小到大枚举所有边
    • 如果某条边的两个端点不连通的划(用并查集判断是否连通),就把这条边加入到最小生成树,(并查集中连接这两个点)
  • 最后如果边的数量小于n-1,说明不连通,否则输出最小生成树长度
#include<iostream>
#include<cstdio>
#include <algorithm>

using namespace std;
const int  N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N]; // 并查集使用

struct Edge
{
    int a, b, w;
    bool operator< (const Edge W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges+m);
    int res = 0, cnt = 0; // cnt表示加入的边数
    for(int i = 0; i < m; i++)
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        int fa = find(a), fb = find(b);
        if(fa != fb)
        {
            p[fa] = fb;
            res += w;
            cnt++;
        }
    }

    if(cnt < n-1) return INF;
    else    return res;

}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++)
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }

    for(int i = 1; i <= n; i++) p[i] = i; // 初始化并查集
    int  res = kruskal();
    if(res == INF) printf("impossible\n");
    else printf("%d\n", res);
    return 0;
}

二分图的判定

使用染色法判断二分图

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。

请你判断这个图是否是二分图。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 u 和 v,表示点 u 和点 v 之间存在一条边。

输出格式

如果给定图是二分图,则输出 Yes,否则输出 No

数据范围

1≤n,m≤105

输入样例:

4 4
1 3
1 4
2 3
2 4

输出样例:

Yes
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e5 + 0;
const int M = N << 1;

int color[N]; // 1表示黑,2表示红
int h[N], e[M], ne[M], idx = 0;
int n,m;

void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool dfs(int u, int c)
{
	color[u] = c;
	
	// 染色 
	for(int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(!color[j])
		{
			bool t = dfs(j, 3 - c);
			if(!t) return false;
		}
		else
		{
			if(color[j] == c) return false;	
		} 
	}
	return true;
}

bool bfs(int u)
{
	queue<int> q;
	if(!color[u]) color[u] = 1; // 从u开始bfs 
	q.push(u);
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		for(int i = h[t]; ~i; i = ne[i])
		{
			int j = e[i];	
			// if(st[j]) continue;
			
			// 没染色就对他染色 
			if(!color[j])
			{
				color[j] = 3 - color[t];
				q.push(j);
			}
			else
			{
				if(color[j] == color[t]) return false;
			}
		}
	}
	return true;
}

int main()
{
	memset(h, -1, sizeof(h));
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b), add(b, a); // 无向图 
	}
	
	bool flag = true;
/*
	for(int i = 1; i <= n; i++)
	{
		if(!color[i])
		{
			bool t = dfs(i, 1); 
			if(!t) 
			{
				flag = false;
				break;	
			}	
		}	
	}
*/ 
	for(int i = 1; i <= n; i++)
	{
		if(!color[i])
		{
			if(!bfs(i))
			{
				flag = false;
				break;
			}
		}
	}
	
	if(flag) puts("Yes");
	else 	 puts("No");
	return 0; 
} 

二分图最大匹配——匈牙利算法

给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 mm 条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

输入格式

第一行包含三个整数 n1、 n2 和 m。

接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。

输出格式

输出一个整数,表示二分图的最大匹配数。

数据范围

1≤n1,n2≤500
1≤u≤n1
1≤v≤n2
1≤m≤10^5

输入样例:

2 2 4
1 1
1 2
2 1
2 2

输出样例:

2
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 510, M = 1e5 + 10;
int h[N], e[M], ne[M], idx = 0;
bool st[N];
int math[N]; 
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool find(int x)
{
	for(int i = h[x]; i != -1; i = ne[i])
	{
		int j = e[i];
		
		// j没有被访问过 
		if(!st[j])
		{
			st[j] = true; // 这里为什么不用回溯? 
			if(math[j] == 0 || find(math[j]))
			{
				math[j] = x;
				return true;	
			} 
		}
	}
	
	return false;
}


int main()
{
	memset(h, -1, sizeof h);
	
	int n1, n2, m;
	scanf("%d%d%d", &n1, &n2, &m);
	for(int i = 1; i <= m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	int res = 0;
	for(int i = 1; i <= n1; i++)
	{
	    memset(st, 0, sizeof st);
	    if(find(i)) res++;
	}
// 		if(find(i)) res++;
		
	printf("%d\n", res);
	return 0;
} 

数学知识

快速幂

给定 n 组 ai,bi,pi,对于每组数据,求出 a^b % p 的值。

typedef long long ll;
ll ksm(int a, int b, int p)
{
    ll res = 1;
    ll t = a;
    while(b)
    {
        if(b & 1) res = (res * t) % p; // 判断每一位 

        t = (t * t) % p; // 累乘 
        b >>= 1;
    }
    return res;
}

逆元

给定 n 组 ai,pi其中 pi是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;

ll ksm(int a, int b, int p)
{
    ll res = 1;
    ll t = (ll)a;
    while(b)
    {
        if(b&1) res = (res * t) % p;

        t = (t * t) % p;
        b >>= 1;
    }
    return res;
}

int main()
{
    int n;
    int a, p;

    scanf("%d", &n);
    while(n--)
    {
        scanf("%d%d", &a, &p);
        if(a % p == 0) puts("impossible"); // 数据保证了p是质数,判断a和p是否互质,只需要看p是不是a的因子
        else    printf("%d\n", ksm(a, p-2, p));
    }
    return 0;

}

试除法判断质数

给定 n 个正整数 ai,判定每个数是否是质数。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

bool isPrim(int x)
{
    if(x < 2) return false;
    for(int i = 2; i <= x / i; i++) // 一定是 <=
    {
        if(x % i == 0) return false;
    }

    return true;
}

int main()
{
    int n;
    scanf("%d", &n);
    while(n --)
    {
        int x;
        scanf("%d", &x);
        if(isPrim(x)) cout << "Yes" << endl;
        else          cout << "No" << endl;
    }
    return 0;
}

分解质因数

给定 n 个正整数 ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int main()
{
    int n;
    scanf("%d", &n);
    while(n --)
    {
        int x;
        scanf("%d", &x);

        for(int i = 2; i <= x / i; i++)
        {

            if(x % i == 0)  // 说明i是x的质因数,如果2是x的质因数,那么当i=4的时候一定不是此时x的因数,所以不必担心
            {
                int cnt = 0;
                while(x % i == 0)
                {
                    cnt ++;
                    x /= i;
                }
                printf("%d %d\n", i, cnt);
            }
        }
        if(x > 1) printf("%d 1\n", x, 1);
        
        puts("");
    }

    return 0;
}

质数筛

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 1000010;
int cnt = 0;
int primes[N];
int st[N]; // st[i] = false 表示是质数


// 朴素筛法
void get_primes(int n) // 得到从1到n的所有质数
{
    for(int i = 2; i <= n; i++)
    {
        if(st[i]) continue; // 标记过了,说明是合数,直接跳过
        
        primes[cnt ++] = i; // 没有标记,说明i是质数
        //把质数i的所有倍数都筛掉
        for(int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}

// 线性筛法
void get_primes_2(int n)
{
    for(int i = 2; i <= n; i++)
    {
        if(!st[i]) primes[cnt++] = i;
        
        for(int j = 0; primes[j] <= n / i; j++)
        {
            st[i * primes[j]] = true;
            if(i % primes[j] == 0) break;
        }
    }
    
}


int main()
{
    int n;
    cin >> n;
    get_primes_2(n);
    cout << cnt << endl;
    return 0;
}


最大公约数

求a, b 的最大公约数,辗转相除法

int gcd(int a, int b)
{
    return b ? gcd(b, a%b) : a;
}

//int gcd(int a,int b)   //最大公约数
//{
//    if(b==0)  return a;
//    else return gcd(b,a%b);
//}

         
int lcm(int a,int b)  //最小公倍数
{
    return a/gcd(a,b)*b;    //防止溢出
}

求组合数

给定 n 组询问,每组询问给定两个整数 a,b,请你输出 c[a][b] % mod 的值。

主要根据数据范围来选择使用什么算法

  1. 当 a,b <= 2000:直接预处理得到所有2000以内的c[a][b],使用递推公式:\(c_a^b = c_{a-1}^b + c_{a-1}^{b-1}\),这个很好理解,假设篮子里有a个苹果,其中有一个红苹果,根据选红苹果或者不选分为两类,就是上面两个公式;注意当b=0的时候,有\(c_a^0 = 1\)

  2. 当a,b <= 100000:使用 \(c_a^b = \frac{a!}{b! * (a-b)!} \% mod= (a! * (b!)^{-1} * (a-b!)^{-1}) % mod\),其中-1表示的是逆元(可以用快速幂来求)

  3. 当a,b <= 10^18:使用卢卡斯定理\(C_a^b = C_{a\%p}^{b\%p} * C_{a/p}^{b/p} (\% p)\)

    typedef long long ll;
    ll lucas(ll a, ll b, int p)
    {
        if(a < p && b < p) return C(a, b, p);
        return C(a%p, b%p, p) * lucas(a/p, b/p, p) % p; // 这里是递归
    }
    

情况4:更大(暂时不看了)需要高精度

情况1:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2010;
const int mod = 1e9 + 7;
int c[N][N];
int n;

void init() // 打表把所有2000以内的c[i][j]都求出来
{
    for(int i = 0; i < N; i++) 
    {
        for(int j = 0; j <= i; j++) 
        {
            if(!j) c[i][j] = 1; // 当j=0,就是从i个里面选0个,一共1种选法
            
            else   c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod;
        }
    }
}

int main()
{
    scanf("%d", &n);
    
    init();
    while(n --)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        
        cout << c[a][b] << endl;
    }
    return 0;
}

情况2

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;

const int N = 1e5 + 10;
const int mod = 1e9 + 7;

ll fact[N]; // 阶乘
ll infact[N]; // 某个阶乘的逆元

ll ksm(int a, int b, int p)
{
    ll res = 1;
    ll t = a;
    while(b)
    {
        if(b & 1) res = (res * t) % mod;
        
        t = (t * t) % mod;
        b >>= 1;
    }
    return res;
}

int main()
{
    int n;
    scanf("%d", &n);
    
    // 初始化
    fact[0] = infact[0] = 1;
    // 求阶乘
    for(int i = 1; i <= N; i++)
    {
        fact[i] = (fact[i-1] * i) % mod;
        infact[i] = ksm(fact[i], mod - 2, mod) % mod;
    }
    
    
    while(n--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%lld\n", (fact[a] * infact[b] % mod ) * infact[a-b] % mod );
    }
    return 0;
    
}

情况3

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;

int p; // 定义成全局变量,因为各个函数都用得到 

ll ksm(int a, int b, int p)
{
	ll res = 1;
	ll t = a;
	while(b)
	{
		if(b & 1) res = (res * t) % p;
		
		t = (t * t) % p;
		b >>= 1;
	}
	return res;
}


// 要保证 a < p && b < p 才有逆元 
ll C(int a, int b, int p)
{
	if(a < b) return 0;// c_a ^ b
	
	ll res = 1;
	// j: a -> a-b+1,  i: 1 -> b
	for(int i = 1, j = a; i <= b; i++, j--)
	{
		res = (ll)(res * j) % p; // res * j
		res = (ll)(res * ksm(i, p-2, p)) % p; // res * j * inv(i)
	}
	return res;	
}


ll lucas(ll a, ll b, int p)
{
	if(a < p && b < p) return C(a, b, p);
	
	return C(a%p, b%p, p) * lucas(a/p, b/p, p) % p;  // 卢卡斯定理 
}

int main()
{
	int n;
	scanf("%d", &n);
	while(n--)
	{
		ll a, b, p;
		scanf("%lld%lld%lld", &a, &b, &p);
		printf("%lld\n", lucas(a, b, p));
	}
	return 0;
} 

from yxc

#include <iostream>
#include <algorithm>

using namespace std;

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;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}


int C(int a, int b, int p)
{
    if (b > a) return 0;

    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (LL)res * j % p;
        res = (LL)res * qmi(i, p - 2, p) % p;
    }
    return res;
}


int lucas(LL a, LL b, int p)
{
    if (a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}


int main()
{
    int n;
    cin >> n;

    while (n -- )
    {
        LL a, b;
        int p;
        cin >> a >> b >> p;
        cout << lucas(a, b, p) << endl;
    }

    return 0;
}

扩展欧几里得算法

给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 \(a_i×x_i+b_i×y_i=gcd(a_i,b_i)\)

输入格式

第一行包含整数 n。

接下来 n 行,每行包含两个整数 ai,bi。

输出格式

输出共 n 行,对于每组ai,bi,求出一组满足条件的 xi,yi,每组结果占一行。

本题答案不唯一,输出任意满足条件的 xi,yi 均可。

数据范围

1≤n≤105
1≤ai,bi≤2×109

输入样例:

2
4 6
8 18

输出样例:

-1 1
-2 1
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int n;

int gcd(int a, int b)
{
    if(!b) return a;
    
    return gcd(b, a % b);
}

int exgcd(int a, int b, int &x, int &y)
{
    if(!b)
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main()
{
    scanf("%d", &n);
    while(n --)
    {
        int a, b, x, y;
        scanf("%d%d", &a, &b);
        exgcd(a, b, x, y);
        printf("%d %d\n", x, y);
    }
    return 0;
}

欧拉函数

#include<iostream>
using namespace std;

int phi(int x)
{
    int res = x;
    for(int i = 2; i <= x/i; i++)
    {
        // 找到一个质因子
        if(x % i == 0)
        {
            res = res / i * (i-1); // 即 res*(1 - 1/i);
            while(x % i == 0) x /= i;
        }
    }
    if(x > 1) res = res / x * (x - 1);
    return res;
}


int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        cin >> x;
        cout << phi(x) << xendl;
    }

    return 0;
}

线性筛法求欧拉函数

给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1000010;


int primes[N], cnt;
int euler[N];
bool st[N];


void get_eulers(int n)
{
    euler[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)
            {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}


int main()
{
    int n;
    cin >> n;

    get_eulers(n);

    LL res = 0;
    for (int i = 1; i <= n; i ++ ) res += euler[i];

    cout << res << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/49995/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

NIM游戏

给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 n。

第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1≤n≤105
1≤每堆石子数≤109

输入样例:

2
2 3

输出样例:

Yes

#include<iostream>
using namespace std;
int n;

int main()
{
    scanf("%d", &n);
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        int a; 
        scanf("%d", &a);
        res ^= a;
    }
    if(res) puts("Yes");
    else    puts("No");
    return 0;
}

动态规划

数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

     7
   3   8
 8   1   0
2   7   4   4
4   5   2   6   5

输入格式

第一行包含整数 n,表示数字三角形的层数。

接下来 n 行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。

输出格式

输出一个整数,表示最大的路径数字和。

输入样例:

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

输出样例:

30
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 510;
int a[N][N], f[N][N];

int INF = 1e9;

int main()
{
    
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= i; j++) 
            scanf("%d", &a[i][j]);
    
    // 初始化
    // 注意初始化的时候要把周围的都初始化
    for(int i = 0; i <= n; i++)// 一定要到i+1
        for(int j = 0; j <= i + 1; j++) f[i][j] = -INF;
    
    f[1][1] = a[1][1];
    // 从上往下!
    for(int i = 2; i <= n; i++) 
        for(int j = 1; j <= i; j++) 
        {
            f[i][j] = max(f[i-1][j-1], f[i-1][j]) + a[i][j];
        }
    
    int res = -INF;
    for(int i = 1; i <= n; i++) res = max(res, f[n][i]);
    
    printf("%d\n", res);
    return 0;
    
}

背包

01背包

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &v[i], &w[i]);
    }
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = f[i-1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
        }
    }
    printf("%d\n", f[n][m]);
    return 0;
    
}

一维优化

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &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]);
    }
    printf("%d\n", f[m]);
    return 0;
    
}

完全背包

求最大值

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

二维

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 1010;
int f[N][N];
int v[N], w[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = f[i-1][j];
            if(j >= v[i])
                f[i][j] = max(f[i][j] , f[i][j-v[i]] + w[i]); 
            
            // for(int k = 0; k * v[i] <= j; k++)
            // {
            //     f[i][j] = max(f[i][j], f[i-1][j-k*v[i]] + k*w[i]);
            // }
        }
    }
    printf("%d\n", f[n][m]);
    return 0;
}

一维优化

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

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 = v[i]; j <= m; j ++ )
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/57825/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
求总方案数

题目描述

一个正整数n可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中n1≥n2≥…≥nk,k≥1

我们将这样的一种表示称为正整数n的一种划分。

现在给定一个正整数n,请你求出n共有多少种不同的划分方法。

输入格式

共一行,包含一个整数n。

输出格式

共一行,包含一个整数,表示总划分数量。

由于答案可能很大,输出结果请对10^9+7取模。

数据范围

1≤n≤1000

输入样例:

5
输出样例:

7

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;

const int N = 1010;
const int mod = 1e9+7;

int n;
int f[N][N];

int main()
{
    scanf("%d", &n);
    
    // 初始化
    f[0][0] = 1;
    
    //
    for(int i = 1; i <= n; i++) // 前i件物品
    {
        for(int j = 0; j <= n; j++) // 容量为j;一定从0开始
        {
            for(int k = 0; k * i <= j; k++) // 第i件物品选k件
            {
                f[i][j] = (f[i][j] + f[i-1][j-k*i]) % mod;
            }
        }
    }
    
    printf("%d\n", f[n][n]);
    return 0;
}

多重背包

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

#include<iostream>
#include<cstdio>
using namespace std;

const int N = 110;

int n, m;
int f[N][N], v[N], w[N], s[N];

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) cin >> v[i] >> w[i] >> s[i];
	
	for(int i = 1; i <= n; i++) // 前i件物品 
		for(int j = 0; j <= m; j++) // 背包容量 
		{
			for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
			    f[i][j] = max(f[i][j], f[i-1][j-k*v[i]]+k*w[i]);
		} 
	
	cout << f[n][m] << endl; 
	
	return 0;
}
 

二进制优化

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 12010; // n*log(s)

int cnt = 0; // 每种物品拆分后,一共形成的组数,最后就是01背包问题
int v[N], w[N];
int n, m;


int f[N]; // 01背包的一维优化,f[i]表示的是

int main()
{
    scanf("%d%d", &n, &m);
    
    for(int i = 1; i <= n; i++)
    {
        int vx, wx, s;
        scanf("%d%d%d", &vx, &wx, &s);
        
        int k = 1;
        while(k <= s) // 这里是 k <= s
        {
            cnt++; // 总组数+1
            v[cnt] = vx * k;
            w[cnt] = wx * k;
            
             
            s -= k;
            k *= 2;
        }
        
        // 如果s按照 1 2 4 8 …… 拆分后还剩下,就直接加入到里面
        if(s > 0)
        {
            cnt++; // 总组数+1
            v[cnt] = vx * s;
            w[cnt] = wx * s;
            
        }
    }
    
    n = cnt; // 拆分后相当于现在有cnt个物品了,然后决定每个物品选还是不选
    
    // 把所有的物品都拆分了,放入到了v[], w[]中,下面就可以用01背包来遍历实现最大值了
    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]);
        }

    printf("%d\n", f[m]);
    return 0;
    
}

分组背包

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 110;

int n, m;
int v[N][N], w[N][N]; // v[i][j] 表示第i组第j个物品
int s[N];

int f[N]; // f[i][j] 表示前i组物品,容量为j的情况下的最大值

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        
        scanf("%d", &s[i]);
        
        for(int j = 1; j <= s[i]; j++)
        {
            scanf("%d%d", &v[i][j], &w[i][j]);
        }
    }
    
    for(int i = 1; i <= n; i++) // 循环分组
    
        for(int j = m; j >= 0; j--) // 注意体积从大到小枚举!!01背包
        {
            for(int k = 1; k <= s[i]; k++) // 每个分组内部选择第k个物品
            {
                if(j >= v[i][k]) f[j] = max(f[j], f[j-v[i][k]] + w[i][k]);
            }
        }
    cout << f[m];
    return 0;
    
}

区间DP

例题:acwing 282. 石子合并

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;

如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式
第一行一个数 N 表示石子的堆数 N。

第二行 N 个数,表示每堆石子的质量(均不超过 1000)。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤N≤300
输入样例:
4
1 3 5 2
输出样例:
22

模板

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 310;
const int INF = 0x3f3f3f3f;
int a[N], s[N]; // s[i]是前i堆石子的前缀和
int f[N][N]; // f[i][j] 表示的是从第i堆合并到第j堆,最小代价
int n;
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    // 计算合并的前缀和
    for(int i = 1; i <= n; i++) s[i] = s[i-1] + a[i];
    
    // 初始化, 区间长度为1时
    for(int i = 1; i <= n; i++) f[i][i] = 0;
    
    // 要按照区间从小到大得到 f[1][n],直接从2开始
    for(int len = 2; len <= n; len++) // k表示枚举区间的长度
    {
        for(int i = 1; i + len -1 <= n; i++) //枚举区间的左端点
        {
            int l = i, r = i + len - 1; // 枚举左右端点
            f[l][r] = INF;
            for(int k = l; k < r; k++)
            {
                f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + s[r] - s[l-1]);  
            }
        }
    }
    printf("%d\n", f[1][n]);
    return 0;
}

贪心

模拟

心态一定要稳!

头文件模板

#include<bits/stdc++.h>
// zwh模板
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
#include<unordered_map>
#include<cstdlib>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;


priority_queue<PII, vector<PII>, greater<PII>> heap;

STL使用

小根堆的定义

priority_queue<PII, vector<PII>, greater<PII>> heap;

稳定排序

bool cmp(int a, int b)
{
    return a < b;
}
stable_sort(a, a+n, cmp);

C++ 标准库cmath数学函数大全

C++头文件声明了一组函数来执行数学运算,例如:sqrt()计算平方根,log()查找数字的自然对数,等等。

方法 描述
abs() 返回参数的绝对值
acos() 返回反余弦数字
acosh() 返回数字的双曲余弦值
asin() 返回反正弦值
asinh() 返回数字的双曲正弦值
atan() 返回反正切数字
atan2() 返回坐标的反正切
atanh() 返回数字的弧双曲正切
cbrt() 计算数字的立方根
ceil() 返回数字的上限值
copysign(x,y) 它以y的符号返回x的大小。
cos() 返回参数的余弦
cosh() 返回某个角度的双曲余弦值
exp() 它计算升为幂x的指数e。
exp2() 它计算x的以2为底的指数。
expm1() 它计算出幂乘以x减一的指数。
fabs() 返回参数的绝对值
fdim(x,y) 返回x和y之间的正差。
floor() 返回十进制数字的下限值
fma(x,y,z) 它计算表达式x * y + z。
fmax() 返回传递的两个参数中最大的
fmin() 返回两个给定参数中的最小值
fmod() 计算除法浮点数的余数
frexp() 返回一个浮点数的尾数和指数。
hypot() 返回参数平方和的平方根
ilogb() 返回| x |的对数的整数部分
ldexp() 将x和2的乘积返回到幂e
llrint() 使用当前舍入模式舍入参数
llround() 将参数四舍五入到最接近的long long int值
log() 返回数字的自然对数
log10() 返回数字的以10为底的对数
log1p() 返回x + 1的自然对数。
log2(x) 它计算x的以2为底的对数。
logb(x) 返回| x |的对数
lrint() 使用当前舍入模式舍入参数
lround() 返回最接近参数的long int值
modf() 将数字分解为整数和小数部分
nan() 返回NaN值
nearbyint() 将参数舍入为使用当前舍入模式
nextafter() 它表示x在y方向上的下一个可表示值。
nexttoward() 它表示x在y方向上的下一个可表示值。
pow() 计算幂
restder(x,y) 返回x / y的余数
remquo(x,y) 计算机余数并存储x / y的商
rint() 使用当前舍入模式舍入参数
round() 返回最接近参数的整数值
scalbln(x,n) 计算x和FLT_RADX乘以n的乘积。
scalbn(x,n) 计算x和FLT_RADX乘以n的乘积。
sin() 返回参数的正弦
sinh() 返回某个角度的双曲正弦
sqrt() 计算数字的平方根
tan() 返回参数的切线
tanh() 返回角度的双曲正切
trunc() 截断数字的符号部分

bitset

bitset存储二进制数位。
bitset就像一个bool类型的数组一样,但是有空间优化——bitset中的一个元素一般只占1 bit,相当于一个char元素所占空间的八分之一。
bitset中的每个元素都能单独被访问,例如对于一个叫做foo的bitset,表达式foo[3]访问了它的第4个元素,就像数组一样。
bitset有一个特性:整数类型和布尔数组都能转化成bitset。
bitset的大小在编译时就需要确定。如果你想要不确定长度的bitset,请使用(奇葩的)vector

构造函数

#include<bitset>
std::bitset<4> foo; //创建一个4位的位集,每一位默认为0
当整数的大小小于位数时,高位填充为0
std::bitset<4> foo(5);  //用整数初始化  5二进制位:101    foo值:0101
当整数的大小超过位数时,从整数二进制的低位开始赋值,高位被舍弃
std::bitset<4> foo(19);  //用整数初始化,19二进制位:10011     foo值:1100
std::bitset<4> foo(std:;string("0101")); //字符串初始化,字符串中必须只能含有‘0’/‘1’
————————————————
版权声明:本文为CSDN博主「风知我意否」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ywh15387127537/article/details/88707044

用法

位运算都可以用: 与、或、非、异或,左移,右移
foo&foo2
foo|foo2
~foo
foo^foo2
foo<<=2
foo>>=2
foo.size() 返回大小(位数)
foo.count() 返回1的个数
foo.any() 返回是否有1
foo.none() 返回是否没有1
foo.set() 全都变成1
foo.set(p) 将第p + 1位变成1
foo.set(p, x) 将第p + 1位变成x
foo.reset() 全都变成0
foo.reset(p) 将第p + 1位变成0
foo.flip() 全都取反
foo.flip(p) 将第p + 1位取反
foo.to_ulong() 返回它转换为unsigned long的结果,如果超出范围则报错
foo.to_ullong() 返回它转换为unsigned long long的结果,如果超出范围则报错
foo.to_string() 返回它转换为string的结果
————————————————
版权声明:本文为CSDN博主「风知我意否」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ywh15387127537/article/details/88707044

容器

vector, 变长数组,倍增的思想
    size()  返回元素个数
    empty()  返回是否为空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    支持比较运算,按字典序

pair<int, int>
    first, 第一个元素
    second, 第二个元素
    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

string,字符串
    size()/length()  返回字符串长度
    empty()
    clear()
    substr(起始下标,(子串长度))  返回子串
    c_str()  返回字符串所在字符数组的起始地址

queue, 队列
    size()
    empty()
    push()  向队尾插入一个元素
    front()  返回队头元素
    back()  返回队尾元素
    pop()  弹出队头元素

priority_queue, 优先队列,默认是大根堆
    size()
    empty()
    push()  插入一个元素
    top()  返回堆顶元素
    pop()  弹出堆顶元素
    定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;

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位取反

常用STL总结

一、栈(stack)

stack实现了一种先进后出的数据结构,使用时需要包含stack头文件

C++定义stack语法:
stack<int> s;//int为栈的数据类型,可以为string,double等
C++中stack的基本操作有:

1、出栈:如 s.pop() 注意并不返回出栈的元素
2、进栈:如 s.push(x)
3、访问栈顶元素:如s.top();
4、判断栈空:如 s.empty() 栈为空时返回true
5、返回栈中元素个数,如:s.size()

#include <iostream>
#include <stack>
using namespace std;
 
int main()
{
    std::stack<int> s;      //定义一个int类型的stack
    for(int i=0;i<10;i++)   //将数字0~9依次入栈
        s.push(i);
 
    std::cout << s.empty() << std::endl; //将输出0,表示栈不为空
 
    for(int i=0;i<10;i++)
    {
        std::cout << s.top() << std::endl;//输出栈顶的元素,依次为9、8、...、0
        s.pop();    //出栈
    }
    std::cout << s.empty() << std::endl;//将输出1,表示栈为空
    return 0;
}

二、动态数组(vector)

C++中的vector是一个可以改变大小的数组,当解题时无法知道自己需要的数组规模有多大时可以用vector来达到最大节约空间的目的。使用时需要包含vector头文件。

定义一个一维动态数组的语法为
vector<int> a; //int为该动态数组的元素数据类型,可以为string、double等
定义一个二维动态数组的语法为
vector<int*> a; //三维数据类型为int**,以此类推。
C++中vector的基本操作有:
1、push_back(x) 在数组的最后添加元素x。
2、pop_back() 删除最后一个元素,无返回值。
3、at(i) 返回位置i的元素。
4、begin() 返回一个迭代器,指向第一个元素。
5、end() 返回一个迭代器,指向最后一个元素的下一个位置。
6、front() 返回数组头的引用。
7、capacity(x) 为vector分配空间
8、size() 返回数组大小
9、resize(x) 改变数组大小,如果x比之前分配的空间大,则自动填充默认值。
10、insert 插入元素
①a.insert(a.begin(),10); 将10插入到a的起始位置前。
②a.insert(a.begin(),3,10) 将10插入到数组位置的0-2处。
11、erase 删除元素
①a.erase(a.begin()); 将起始位置的元素删除。
②a.erase(a.begin(),begin()+2); 将0~2之间的元素删除。
12、rbegin() 返回一个逆序迭代器,它指向最后一个元素。
13、rend() 返回一个逆序迭代器,它指向的第一个元素前面的位置。
14、clear()清空所有元素。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int main()
{
    vector<int> a;
    vector<int> b;
 
    for (int i = 0; i < 10; i++)    //向数组a依次添加0~9
        a.push_back(i);
 
    a.swap(b);      //将数组a元素与数组b元素交换
    cout << a.size() << " " << b.size() << endl;    //此时应当输出 0 10
 
    for (vector<int>::iterator it = b.begin(); it != b.end(); it++)//从第一个元素开始遍历数组元素
        cout << *it << " ";     //依次输出0~9
    cout << endl;
 
    b.erase(b.begin() + 1);     //删除位置1的元素,即元素1.
    cout << b.size() << endl;   //由于删除了一个元素,此时输出应当为8
 
    for (vector<int>::reverse_iterator rit = b.rbegin(); rit != b.rend(); ++rit)//逆向输出数组元素
        cout << *rit << " ";    //应当输出9 8 7 6 5 4 3 2 0
    cout << endl;
 
    b.resize(9);    //将数组空间设定为9,相当于比之前多了1个位置
    b.push_back(20);//在尾部添加元素20
 
    for (vector<int>::iterator it = b.begin(); it != b.end(); it++)
        cout << *it << " ";  //应当输出0 2 3 4 5 6 7 8 9 20
 
    return 0;
}

三、集合(set)

C++中集合(set)类似于数学上的集合,即每个元素只能出现一次,使用该容器需要包含set头文件。

定义一个set的语法为:
set<int> s; //int为集合的数据类型,可以为string,double等
C++中set的基本操作有:
1、begin() 返回一个迭代器,指向第一个元素。
2、end() 返回一个迭代器,指向最后一个元素的下一个位置。
3、clear()清空set的所有元素。
4、empty() 判断是否为空。
5、size() 返回当前元素个数
6、erase(it) 删除迭代器指针it指向的元素。
7、insert(a) 插入元素a
8、count() 查找某个元素出现的次数,只有可能为0或1。
9、find() 查找某个元素出现的位置,如果找到则返回这个元素的迭代器,如果不存在,则返回s.end()

四、队列(queue)

queue实现了一种先进先出的数据结构,使用时需要包含queue头文件。

定义一个queue的语法为:
queue<int> q; //int为队列的数据类型,可以为string,double等
C++中queue的基本操作有:
1、入队,如:q.push(x) 将元素x置于队列的末端
2、出队,如: q.pop() 同样不会返回弹出元素的值
3、返回队首元素,如:q.front();
4、返回队尾元素,如:q.back();
5、判断是否为空,如:q.empty();
6、返回队列元素个数,如:q.size();

#include <iostream>
#include <queue>
using namespace std;
 
int main()
{
    queue<int> q;
    for(int i = 0;i < 10;i++)   //将0~9依次入队
        q.push(i);
 
    cout << q.front() << " " << q.back() << endl; //这里应当输出0和9
 
    //依次输出0、1、...、9
    for(int i = 0;i < 10;i++)
    {
        cout << q.front() << " ";
        q.pop();
    }
    return 0;
}

C++ STL

本文整理了常用到的C++ STL,用于自查。
(要想更好的 A 题,还需要对这些知识的熟练掌握和灵活运用。
现整理如下

// size和empty是所有容器都有的方法
    size()  返回元素个数
    empty()  返回是否为空

vector

vector
// 变长数组,自动增长
    // 定义 一个长度为10的vector 每一个值为-3
    vector<int> a(10, -3);
	vector<int> a[10];
	// size和empty是所有容器都有的方法
    size()  返回元素个数
    empty()  返回是否为空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    // 迭代器
    begin()/end()
    // 支持比较运算,按字典序对所有元素排序
        
// 示例代码:
int main() {
	vector<int> v;
	for(int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	for(int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
	for(vector<int>::iterator i = v.begin(); i != v.end(); i++) {
		cout << *i << " ";
	}
	cout << endl;
	for(auto x: v) {
		cout << x << " ";
	}
	cout << endl;
	return 0;
}    

pair

pair<int, string>
// 存储二元组 适用于顶替有比较的结构体
    p.first; 第一个元素
    p.second, 第二个元素
    // 支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
    p = make_pair(10, "zjy");
	p = {20, "zjy"};

string

string
// 字符串
    size()/length()  返回字符串长度
    empty()
    clear()
    substr(起始下标,子串长度)  返回子串
    c_str()  返回字符串所在字符数组的起始地址
    // c_str可以用来输出
    printf("%s", s.c_str);
	
	back()	取最后一个元素
	push_back(c)
	pop_back()

queue

queue,
// 队列
    size()
    empty()
    push()  向队尾插入一个元素
    front() 返回队头元素
    back() 	返回队尾元素
    pop() 	弹出队头元素
    // 如果向清空这个队列q,重新构造即可
    queue<int>();

priority_queue

priority_queue
// 优先队列,默认是大根堆
    size()
    empty()
    push()  插入一个元素
    top()  返回堆顶元素
    pop()  弹出堆顶元素
    // 定义成小根堆的方式
    priority_queue<int, vector<int>, greater<int>> q;

stack

stack
// 栈
    size()
    empty()
    push()	向栈顶插入一个元素
    top() 	返回栈顶元素
    pop()	弹出栈顶元素

deque

deque, 双端队列
// 使用较少 效率较低
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()

set

set/multiset
// set里不能有重复元素
// multiset可以有重复元素
    insert()	插入一个数
    find()		查找一个数
    count() 	返回某一个数的个数
    erase()
    (1) 输入是一个数x,删除所有x   
    	O(k + logn)
    (2) 输入一个迭代器,删除这个迭代器
    lower_bound()/upper_bound()
    // 返回大于等于x的最小的数的迭代器
    lower_bound(x)  
    // 返回大于x的最小的数的迭代器
    upper_bound(x)  

map/multimap

map/multimap
    insert()	参数是一个pair
    erase() 	参数是pair或者迭代器
    find()
    []   
/*
int main() {
	map<string, int> mapp;
	mapp["zjy"] = 14;
	cout << mapp["zjy"] << endl;
	return 0;
}
multimap不支持此操作 时间复杂度是 O(logn)
*/
    lower_bound()/upper_bound()

set, map, multiset, multimap, 基于平衡二叉树(**红黑树**),本质是动态维护有序序列
    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驱和后继,时间复杂度 O(logn)
    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位取反

posted @ 2022-03-24 20:41  VanHope  阅读(3569)  评论(0编辑  收藏  举报