AcWing算法基础课 | AcWing基础课题集汇总,超详细笔记整理

时间:2025-04-12 19:15:26

本篇博文是笔者归纳汇总的AcWing基础课题集,方便读者后期复盘巩固~

PS:本篇文章只给出完整的算法实现,并没有讲解具体的算法思路。如果想看算法思路,可以阅读笔者往期写过的文章(或许会有),也可以移步AcWing官网看详情。

本篇文章的特点:每道题会给出多种解法,不局限于一种思路,可以帮助读者从不同角度思考题目。算法代码简洁,适合背诵记忆~

完整笔记已上传至GitHub:传送门
欢迎Star

第一讲 基础算法

快速排序

AcWing785.快速排序

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], n;

void quick_sort(int l, int r)
{
    if (l >= r) return ;
    swap(a[l], a[l + rand() % (r - l + 1)]);
    int i = l - 1, j = r + 1, x = a[l];
    while (i < j)
    {
        while (a[ ++ i] < x)    ;
        while (a[ -- j] > x)    ;
        if (i < j)  swap(a[i], a[j]);
    }
    quick_sort(l, j);
    quick_sort(j + 1, r);
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    quick_sort(0, n - 1);
    for (int i = 0; i < n; i ++ )   printf("%d ", a[i]);
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], n;

void quick_sort(int l, int r)
{
    if (l >= r) return ;
    swap(a[l], a[l + rand() % (r - l + 1)]);
    int i = l, j = r, x = a[l];
    while (i < j)
    {
        while (i < j && a[j] > x)   j -- ;
        if (i < j)  a[i ++ ] = a[j];
        
        while (i < j && a[i] < x)   i ++ ;
        if (i < j)  a[j -- ] = a[i];
    }
    a[i] = x;
    quick_sort(l, i - 1);
    quick_sort(i + 1, r);
}

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

AcWing786. 第k个数

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], n, k;

int quick_select(int l, int r, int k)
{
    if (l >= r) return a[l];
    swap(a[l], a[l + rand() % (r - l + 1)]);
    int i = l - 1, j = r + 1, x = a[l];
    while (i < j)
    {
        while (a[ ++ i] < x)    ;
        while (a[ -- j] > x)    ;
        if (i < j)  swap(a[i], a[j]);
    }
    int s = j - l + 1;
    if (k <= s) return quick_select(l, j, k);
    return quick_select(j + 1, r, k - s);
}

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    printf("%d\n", quick_select(0, n - 1, k));
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], n, k;

int quick_select(int l, int r, int k)
{
    swap(a[l], a[l + rand() % (r - l + 1)]);
    int i = l, j = r, x = a[l];
    while (i < j)
    {
        while (i < j && a[j] > x)   j -- ;
        if (i < j)  a[i ++ ] = a[j];
        
        while (i < j && a[i] < x)   i ++ ;
        if (i < j)  a[j -- ] = a[i];
    }
    a[i] = x;
    int s = i - l + 1;  // [l, i]区间个数 i - l + 1
    if (k == s) return a[i];
    if (k < s)  return quick_select(l, i - 1, k);
    return quick_select(i + 1, r, k - s);
}

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

归并排序

AcWing787. 归并排序

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], tmp[N], n;

void merge_sort(int l, int r)
{
    if (l >= r) return ;
    
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);
    
    int i = l, j = mid + 1, k = l;
    while (i <= mid && j <= r)
    {
        if (a[i] <= a[j])   tmp[k ++ ] = a[i ++ ];
        else    tmp[k ++ ] = a[j ++ ];
    }
    while (i <= mid)    tmp[k ++ ] = a[i ++ ];
    while (j <= r)  tmp[k ++ ] = a[j ++ ];
    for (int i = l; i <= r; i ++ )  a[i] = tmp[i];
}

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

AcWing788. 逆序对的数量

传送门

// 归并排序
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
using LL = long long ;
int a[N], tmp[N], n;

LL merge_sort(int l, int r)
{
    if (l >= r) return 0;
    
    int mid = l + r >> 1;
    LL res = merge_sort(l, mid) + merge_sort(mid + 1, r);
    
    int i = l, j = mid + 1, k = l;
    while (i <= mid && j <= r)
    {
        if (a[i] > a[j])
        {
            res += mid - i + 1;
            tmp[k ++ ] = a[j ++ ];
        }
        else    tmp[k ++ ] = a[i ++ ];
    }
    while (i <= mid)    tmp[k ++ ] = a[i ++ ];
    while (j <= r)  tmp[k ++ ] = a[j ++ ];
    for (int i = l; i <= r; i ++ )  a[i] = tmp[i];
    
    return res;
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    printf("%lld\n", merge_sort(0, n - 1));
    return 0;
}
// 树状数组:写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
using LL = long long ;
int a[N], b[N], tr[N], n, m;

int find(int x) {
    int l = 1, r = m;
    while (l < r) {
        int mid = l + r >> 1;
        if (b[mid] >= x)    r = mid;
        else    l = mid + 1;
    }
    return l;
}

inline int lowbit(int x) {
    return x & -x;
}

void add(int x, int v) {
    for (int i = x; i <= m; i += lowbit(i)) tr[i] += v;
}

LL query(int x) {
    LL s = 0;
    for (int i = x; i; i -= lowbit(i))  s += tr[i];
    return s;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) {
        scanf("%d", &a[i]);
        b[i] = a[i];
    }
    
    sort(b + 1, b + 1 + n);
    m = unique(b + 1, b + 1 + n) - (b + 1);
    
    LL ans = 0;
    for (int i = 1; i <= n; i ++ ) {
        int x = find(a[i]);
        ans += query(m) - query(x); // 这里也可以写成 i - query(x)
        add(x, 1);
    }
    
    printf("%lld\n", ans);
    return 0;
}

// 树状数组:写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
using LL = long long ;
int n, m;
vector<int> a, b, tr;

inline int lowbit(int x) {
    return x & -x;
}

void add(int x, int v) {
    for (int i = x; i <= m; i += lowbit(i)) tr[i] += v;
}

LL query(int x) {
    LL s = 0;
    for (int i = x; i; i -= lowbit(i))  s += tr[i];
    return s;
}

int main()
{
    scanf("%d", &n);
    (n);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    
    b = a;
    sort((), ());
    (unique((), ()), ());
    m = ();
    tr = vector<int>(m + 1, 0);
    
    LL ans = 0;
    for (int i = 0; i < n; i ++ ) {
        int x = lower_bound((), (), a[i]) - () + 1;
        ans += query(m) - query(x);	// 这里也可以写成 i - query(x)
        add(x, 1);
    }
    
    printf("%lld\n", ans);
    return 0;
}

二分

AcWing789. 数的范围

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    while (m -- )
    {
        int x;
        scanf("%d", &x);
        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (a[mid] >= x)    r = mid;
            else    l = mid + 1;
        }
        if (a[l] != x)    puts("-1 -1");
        else
        {
            printf("%d ", l);
            int l = 0, r = n - 1;
            while (l < r)
            {
                int mid = l + r + 1 >> 1;
                if (a[mid] <= x)    l = mid;
                else    r = mid - 1;
            }
            printf("%d\n", r);
        }
    }
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], n, m;

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

AcWing790. 数的三次方根

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
double n;

int main()
{
    cin >> n;
    double l = -10000, r = 10000;
    while (l + eps < r)
    {
        double mid = (l + r) / 2;
        if (mid * mid * mid > n)    r = mid;
        else    l = mid;
    }
    printf("%lf\n", l);
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
double x;

int main()
{
    scanf("%lf", &x);
    double l = -10000, r = 10000;
    for (int i = 1; i <= 100; i ++ )
    {
        double mid = (l + r) / 2;
        if (mid * mid * mid > x)    r = mid;
        else    l = mid;
    }
    printf("%.6lf\n", l);
    return 0;
}

高精度

AcWing791. 高精度加法

传送门

#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 < () || i < () || t; i ++ )
    {
        if (i < ())   t += A[i];
        if (i < ())   t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }
    return C;
}

int main()
{
    string a, b;
    vector<int> A, B;
    cin >> a >> b;
    
    for (int i = () - 1; i >= 0; i -- )   A.push_back(a[i] - '0');
    for (int i = () - 1; i >= 0; i -- )   B.push_back(b[i] - '0');
    
    auto C = add(A, B);
    for (int i = () - 1; i >= 0; i -- )   printf("%d", C[i]);
    return 0;
}

AcWing792. 高精度减法

传送门

#include <bits/stdc++.h>
using namespace std;

bool cmp(vector<int> A, vector<int> B)
{
    if (() != ())   return () > ();
    
    for (int i = () - 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;
    int t = 0;
    for (int i = 0; i < (); i ++ )
    {
        t = A[i] - t;
        if (i < ())   t -= B[i];
        C.push_back((t + 10) % 10);
        t = t < 0 ? 1 : 0;
    }
    
    while (() > 1 && () == 0)   C.pop_back();
    
    return C;
}

int main()
{
    string a,  b;
    vector<int> A, B;
    cin >> a >> b;
    
    for (int i = () - 1; i >= 0; i -- )   A.push_back(a[i] - '0');
    for (int i = () - 1; i >= 0; i -- )   B.push_back(b[i] - '0');
    
    if (cmp(A, B))
    {
        auto C = sub(A, B);
        for (int i = () - 1; i >= 0; i -- )   printf("%d", C[i]);
    }
    else
    {
        printf("-");
        auto C = sub(B, A);
        for (int i = () - 1; i >= 0; i -- )   printf("%d", C[i]);
    }
}

AcWing793. 高精度乘法

传送门

// 高精度 * 低精度
#include <bits/stdc++.h>
using namespace std;

vector<int> mul(vector<int> A, int b)
{
    vector<int> C;
    int t = 0;
    for (int i = 0; i < () || t; i ++ )
    {
        if (i < ())   t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    
    while (() > 1 && () == 0)   C.pop_back();
    
    return C;
}

int main()
{
    string a;
    int b;
    vector<int> A;
    cin >> a >> b;
    
    for (int i = () - 1; i >= 0; i -- )   A.push_back(a[i] - '0');
    
    auto C = mul(A, b);
    for (int i = () - 1; i >= 0; i -- )   printf("%d", C[i]);
    
    return 0;
}
// 高精度 * 高精度
#include <bits/stdc++.h>
using namespace std;

vector<int> mul(vector<int> A, vector<int> B)
{
    int n = (), m = ();
    vector<int> C(n + m);
    
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            C[i + j] += A[i] * B[j];
    
    for (int i = 0, t = 0; i < (); i ++ )
    {
        t += C[i];
        C[i] = t % 10;
        t /= 10;
    }
    
    while (() > 1 && () == 0)   C.pop_back();
    
    return C;
}

int main()
{
    string a, b;
    vector<int> A, B;
    cin >> a >> b;
    
    for (int i = () - 1; i >= 0; i -- )   A.push_back(a[i] - '0');
    for (int i = () - 1; i >= 0; i -- )   B.push_back(b[i] - '0');
    
    auto C = mul(A, B);
    for (int i = () - 1; i >= 0; i -- )   printf("%d", C[i]);
    
    return 0;
}

AcWing794. 高精度除法

传送门

#include <bits/stdc++.h>
using namespace std;

vector<int> div(vector<int> A, int b, int &r)
{
    vector<int> C;
    r = 0;
    
    for (int i = () - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    
    reverse((), ());
    while (() > 1 && () == 0)   C.pop_back();
    
    return C;
}

int main()
{
    string a;
    int b;
    vector<int> A;
    cin >> a >> b;
    for (int i = () - 1; i >= 0; i -- )   A.push_back(a[i] - '0');
    
    int r;
    auto C = div(A, b, r);
    for (int i = () - 1; i >= 0; i -- )   printf("%d", C[i]);
    printf("\n%d\n", r);
    return 0;
}

前缀和与差分

AcWing795. 前缀和

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int s[N];
int n, m;

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

AcWing796. 子矩阵的和

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int s[N][N], n, m, k;

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            scanf("%d", &s[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];
            
    while (k -- )
    {
        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;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int s[N][N], n, m, k;

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            scanf("%d", &s[i][j]);
    
    // 先求行的前缀和
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            s[i][j] += s[i][j - 1];
            
    // 再对整体求前缀和
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            s[i][j] += s[i - 1][j];
            
    while (k -- )
    {
        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;
}

AcWing797. 差分

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &a[i]);
        b[i] = a[i] - a[i - 1];
    }
    
    while (m -- )
    {
        int l, r, c;
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c, b[r + 1] -= c;
    }
    
    for (int i = 1; i <= n; i ++ )
    {
        a[i] = a[i - 1] + b[i];
        printf("%d ", a[i]);
    }
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n, m;

void ins(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]);
        ins(i, i, a[i]);
    }
    
    while (m -- )
    {
        int l, r, c;
        scanf("%d%d%d", &l, &r, &c);
        ins(l, r, c);
    }
    
    for (int i = 1; i <= n; i ++ )
    {
        a[i] = a[i - 1] + b[i];
        printf("%d ", a[i]);
    }
    
    return 0;
}

AcWing798. 差分矩阵

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N], n, m, k;

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    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 ++ )
            b[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];
    
    while (k -- )
    {
        int x1, y1, x2, y2, c;
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        b[x1][y1] += c;
        b[x1][y2 + 1] -= c;
        b[x2 + 1][y1] -= c;
        b[x2 + 1][y2 + 1] += c;
    }
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            a[i][j] = b[i][j] + a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
    
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++)
            printf("%d ", a[i][j]);
        puts("");
    }
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int s[N][N], n, m, k;

void ins(int x1, int y1, int x2, int y2, int c)
{
    s[x1][y1] += c;
    s[x1][y2 + 1] -= c;
    s[x2 + 1][y1] -= c;
    s[x2 + 1][y2 + 1] += c;
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ )
        {
            int c;
            scanf("%d", &c);
            ins(i, j, i, j, c);
        }
    }
    
    while (k -- )
    {
        int x1, y1, x2, y2, c;
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        ins(x1, y1, x2, y2, c);
    }
    
    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];
    
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++)
            printf("%d ", s[i][j]);
        puts("");
    }
    
    return 0;
}
// 写法3
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int s[N][N], n, m, k;

void ins(int x1, int y1, int x2, int y2, int c)
{
    s[x1][y1] += c;
    s[x1][y2 + 1] -= c;
    s[x2 + 1][y1] -= c;
    s[x2 + 1][y2 + 1] += c;
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ )
        {
            int c;
            scanf("%d", &c);
            ins(i, j, i, j, c);
        }
    }
    
    while (k -- )
    {
        int x1, y1, x2, y2, c;
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        ins(x1, y1, x2, y2, c);
    }
    
    // 先对行求前缀和
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            s[i][j] += s[i][j - 1];
            
    // 再对整体求前缀和
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            s[i][j] += s[i - 1][j];
    
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++)
            printf("%d ", s[i][j]);
        puts("");
    }
    
    return 0;
}

双指针算法

AcWing799. 最长连续不重复子序列

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N], n;

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

AcWing800. 数组元素的目标和

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n, m, x;

int main()
{
    scanf("%d%d%d", &n, &m, &x);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    for (int i = 0; i < m; i ++ )   scanf("%d", &b[i]);
    
    for (int i = 0, j = m - 1; i < n; i ++ )
    {
        while (j >= 0 && a[i] + b[j] > x)   j -- ;
        if (a[i] + b[j] == x)
        {
            printf("%d %d\n", i, j);
            break;
        }
    }
    return 0;
}

AcWing2816. 判断子序列

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    for (int i = 0; i < m; i ++ )   scanf("%d", &b[i]);
    
    int i = 0, j = 0;
    while (i < n && j < m)
    {
        if (a[i] == b[j])   i ++ ;
        j ++ ;
    }
    
    puts(i == n ? "Yes" : "No");
    return 0;
}

位运算

AcWing801. 二进制中1的个数

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
int n;

inline int lowbit(int x)
{
    return x & -x;
}

int main()
{
    scanf("%d", &n);
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        int ans = 0;
        while (x)
        {
            x -= lowbit(x);
            ans ++ ;
        }
        printf("%d ", ans);
    }
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
int n;

inline int lowbit(int x)
{
    return x & -x;
}

int main()
{
    scanf("%d", &n);
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        int ans = 0;
        for (int i = 0; i < 32; i ++ )
            if (x >> i & 1)
                ans ++ ;

        printf("%d ", ans);
    }
    return 0;
}

离散化

AcWing802. 区间和

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
typedef pair<int, int> PII;
vector<PII> add, query;
vector<int> alls;
int s[N], n, m;

int find(int x)
{
    int l = 0, r = () - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else    l = mid + 1;
    }
    // 加1是因为我们想让离散化后的坐标是从1开始  因为前缀和是从下标1开始计算
    return r + 1;   // 其实加不加都无所谓
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )
    {
        int x, c;
        scanf("%d%d", &x, &c);
        add.push_back({x, c});
        alls.push_back(x);
    }
    
    for (int i = 0; i < m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        query.push_back({l, r});
        alls.push_back(l), alls.push_back(r);
    }
    
    // 离散化的核心:排序+去重
    sort((), ()); // 排序
    (unique((), ()), ());   // 去重
    
    for (auto &it : add)
    {
        int nx = find();
        s[nx] += ;
    }
    
    // 前缀和
    for (int i = 1; i <= (); i ++ )  s[i] += s[i - 1];
    
    for (auto &it : query)
    {
        int nl = find(), nr = find();
        printf("%d\n", s[nr] - s[nl - 1]);
    }
    
    return 0;
}

区间合并

AcWing803. 区间合并

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef pair<int, int> PII;
#define x first
#define y second
vector<PII> segs, ans;
int n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        segs.push_back({l, r});
    }
    
    sort((), ());
    int lastL = segs[0].x, lastR = segs[0].y;
    for (int i = 1; i < n; i ++ )
    {
        if (segs[i].x > lastR)
        {
            ans.push_back({lastL, lastR});
            lastL = segs[i].x;
            lastR = segs[i].y;
        }
        else    lastR = max(lastR, segs[i].y);
    }
    ans.push_back({lastL, lastR});
    
    printf("%d\n", ());
    return 0;
}

第二讲 数据结构

单链表

AcWing826. 单链表

传送门

// 写法1:单链表
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int e[N], ne[N], head, idx;
int m;

void init()
{
    head = -1, idx = 0;
}

void add_to_head(int x)
{
    e[idx] = x, ne[idx] = head, head = idx ++ ;
}

void add(int k, int x)
{
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
}

void remove(int k)
{
    ne[k] = ne[ne[k]];
}

int main()
{
    init();
    scanf("%d", &m);
    while (m -- )
    {
        int k, x;
        char op[5];
        scanf("%s", op);
        if (*op == 'H')
        {
            scanf("%d", &x);
            add_to_head(x);
        }
        else if (*op == 'D')
        {
            scanf("%d", &k);
            if (!k )    head = ne[head];
            else    remove(k - 1);
        }
        else
        {
            scanf("%d%d", &k, &x);
            add(k - 1, x);
        }
    }
    
    for (int i = head; ~i; i = ne[i])   printf("%d ", e[i]);
    
    return 0;
}
// 写法2:双链表
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int e[N], ne[N], head, idx;
int m;

void init() // 初始化,默认以[0]为head指针(无实际值). 从[1]开始存第1个结点 
{
    ne[0] = -1, idx = 1;
}

void add(int k, int x)  // 在第[k]个位的右边,插入新元素
{
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
}

void remove(int k)  // 删除第[k]位的右边的元素
{
    ne[k] = ne[ne[k]];
}

int main()
{
    init();
    scanf("%d", &m);
    while (m -- )
    {
        int k, x;   // 在第k个位置,k对应的下标从1,2,3,....,n ; 由于[0]已被head使用,所以不需要k-1
        char op[5];
        scanf("%s", op);
        if (*op == 'H')
        {
            scanf("%d", &x);
            add(0, x);  // [0]的右边即实际第1个结点
        }
        else if (*op == 'D')
        {
            scanf("%d", &k);
            remove(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            add(k, x);
        }
    }
    
    // ne[0] 是第1个实际有值的结点
    for (int i = ne[0]; ~i; i = ne[i])   printf("%d ", e[i]);
    
    return 0;
}

双链表

AcWing827 双链表

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int l[N], r[N], e[N], idx;
int m;

void init()
{
    r[0] = 1, l[1] = 0, idx = 2;
}

void add(int k, int x)
{
    e[idx] = x;
    r[idx] = r[k], l[idx] = k;
    l[r[k]] = idx, r[k] = idx ++ ;
}

void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

int main()
{
    init();
    scanf("%d", &m);
    
    while (m -- )
    {
        int k, x;
        string op;
        cin >> op;
        
        if (op == "L")
        {
            scanf("%d", &x);
            add(0, x);
        }
        else if (op == "R")
        {
            scanf("%d", &x);
            add(l[1], x);
        }
        else if (op == "D")
        {
            scanf("%d", &k);
            remove(k + 1);
        }
        else if (op == "IL")
        {
            scanf("%d%d", &k, &x);
            add(l[k + 1], x);
        }
        else if (op == "IR")
        {
            scanf("%d%d", &k, &x);
            add(k + 1, x);
        }
    }
    
    for (int i = r[0]; i != 1; i = r[i])    printf("%d ", e[i]);
    
    return 0;
}

AcWing828. 模拟栈

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int stk[N], top, m;

int main()
{
    scanf("%d", &m);
    while (m -- )
    {
        int x;
        string op;
        cin >> op;
        if (op == "push")
        {
            scanf("%d", &x);
            stk[++ top ] = x;
        }
        else if (op == "pop")   top -- ;
        else if (op == "empty") {
            puts(top ? "NO" : "YES");
        }
        else    printf("%d\n", stk[top]);
    }
    
    return 0;
}

AcWing3302. 表达式求值

传送门

// 写法1:先将中缀转成后缀,然后对后缀表达式求值
#include <bits/stdc++.h>
using namespace std;
unordered_map<string, int> pr{
        {"(", 0}, {"+", 1}, {"-", 1}, {"*", 2}, {"/", 2}
};
vector<string> post;    // 保存转化后的后缀表达式
string s;

// 将中缀表达式 转换成 后缀表达式
void convert()
{
    int n = ();
    stack<string> stk;  // 在这里栈stk是用来存储运算符的

    for (int i = 0; i < n; i ++ )
    {
        string str;
        // 遇到数字直接加入后缀表达式post中
        if (isdigit(s[i]))
        {
            while (i < n && isdigit(s[i]))  str += s[i ++ ];
            post.push_back(str);
            if (i == n )    break;
        }

        // 遇到左括号直接入栈
        if (s[i] == '(')    ("(");
        // 遇到右括号,将栈顶符号依次弹出并加入后缀表达式post中, 直到碰到匹配的左括号
        else if (s[i] == ')')
        {
            while (!())
            {
                string item = (); ();
                if (item == "(")    break;
                else    post.push_back(item);
            }
        }
        // 遇到运算符号 + - * /
        else
        {
            int s_pr;   // 判断当前运算符的优先级
            if (s[i] == '+' || s[i] == '-' )    s_pr = 1;
            else    s_pr = 2;
            // 若栈顶运算符优先级不低于当前运算符,则将栈顶运算符弹出到post中
            while (!() && pr[()] >= s_pr)
            {
                post.push_back(());
                ();
            }
            (1, s[i]);
        }
    }

    // 遍历结束之后:将栈顶所有的符号弹出加入后缀表达式post中
    while (!())
    {
        post.push_back(());
        ();
    }
}

// 后缀表达式求值
int evalRPN()
{
    stack<int> stk;     // 在这里栈stk是用来存储操作数
    for (string &s : post)
    {
        if (s == "+" || s == "-" || s == "*" || s == "/")
        {
            int b = (); ();
            int a = (); ();
            if (s == "+")   a += b;
            else if (s == "-")  a -= b;
            else if (s == "*")  a *= b;
            else    a /= b;
            (a);
        }
        else    (stoi(s));
    }

    return ();
}

int main()
{
    cin >> s;
    convert();
    printf("%d\n", evalRPN());
    return 0;
}
// 写法2:双栈直接求解中缀表达式
#include<iostream>
#include<cstring>
#include<stack>
#include<algorithm>
#include<unordered_map>
using namespace std;
stack<int> nums;
stack<char> op;
unordered_map<char, int> pr{
    {'(', 0}, {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}
};
string s, str;

void eval()
{
    int b = (); ();
    int a = (); ();
    char c = ();  ();

    if (c == '+')   a += b;
    else if (c == '-')  a -= b;
    else if (c == '*')  a *= b;
    else if (c == '/')  a /= b;

    (a);
}

int main()
{
    cin >> str;
    for (auto &c : str) // 若输入的序列存在空格  则先抛去空格
        if (c != ' ')
            s += c;
            
    int n = ();
    for (int i = 0; i < n; i ++ )
    {
        char c = s[i];
        if (isdigit(c))
        {
            int x = 0, j = i;
            while (j < n && isdigit(s[j]))
            {
                x = x * 10 + s[j] - '0';
                j ++ ;
            }
            i = j - 1;
            (x);
            if (i == n) break;
        }
        else if (c == '(')  (c);
        else if (c == ')')
        {
            while (() != '(' )    eval();
            ();
        }
        else
        {
            // i==0针对的是 +5-6这种数据
            // 后面的是针对 1-(-2)这种把加和减当作正负号的 
            if (!i || s[i - 1] == '(' || s[i - 1] == '+' || s[i - 1] == '-')    (0); 
            while (!() && pr[()] >= pr[c])    eval();
            (c);
        }
    }

    while (!() )    eval();

    printf("%d\n", ());
    return 0;
}

队列

AcWing829. 模拟队列

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
// 队首和队列的初始化:hh = 0, tt = -1 或者 hh = 1, tt = 0 看个人习惯
int q[N], hh, tt = -1, m;	

int main()
{
    scanf("%d", &m);
    while (m -- )
    {
        string op;
        cin >> op;
        if (op == "push")
        {
            int x;
            scanf("%d", &x);
            q[ ++ tt] = x;
        }
        else if (op == "pop")  hh ++ ;
        else if (op == "empty") {
            puts(hh <= tt ? "NO" : "YES");
        }
        else    cout << q[hh] << endl;
    }
    return 0;
}

单调栈

AcWing830. 单调栈

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int stk[N], top, n;

int main()
{
    scanf("%d", &n);
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        while (top && stk[top] >= x)   top -- ;
        if (top)    printf("%d ", stk[top]);
        else    printf("-1 ");
        stk[ ++ top] = x;
    }
    
    return 0;
}

单调队列

AcWing154. 滑动窗口

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N], q[N], n, k;

void getMin()
{
    int hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ )
    {
        if (hh <= tt && q[hh] < i - k + 1)  hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i])    tt -- ;
        q[ ++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    puts("");
}

void getMax()
{
    int hh = 0, tt = -1;
    for (int i = 0; i < n; i ++ )
    {
        if (hh <= tt && q[hh] < i - k + 1)  hh ++ ;
        while (hh <= tt && a[q[tt]] <= a[i])    tt -- ;
        q[ ++ tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    puts("");
}

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    
    getMin();
    getMax();
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N], q[N], n, k;

void getMin()
{
    int hh = 1, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        while (hh <= tt && a[q[tt]] >= a[i]) tt --;
        q[ ++ tt] = i;
        // 不需要写hh<=tt  而且该判断条件必须在while后面而不能在前面
        if (q[hh] < i - k + 1)  hh ++ ;
        if (i >= k) printf("%d ", a[q[hh]]);
    }
    puts("");
}

void getMax()
{
    int hh = 1, tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        while (hh <= tt && a[q[tt]] <= a[i])    tt --;
        q[ ++ tt] = i;
        if (q[hh] < i - k + 1)  hh ++ ;
        if (i >= k) printf("%d ", a[q[hh]]);
    }
    puts("");
}

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i ++ )  scanf("%d", &a[i]);
    getMin();
    getMax();
    return 0;
}

KMP

AcWing831. KMP字符串

传送门

// 下标从0开始的写法
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
char p[N], s[N];    // p是模式串, s是主串
int ne[N], n, m;    // n是主串的长度, m是模式串的长度

void getNext()
{
    ne[0] = 0;
    for (int i = 1, j = 0; i < m; i ++ )
    {
        while (j && p[i] != p[j])   j = ne[j - 1];
        if (p[i] == p[j])   j ++ ;
        ne[i] = j;
    }
}

void KMP()
{
    for (int i = 0, j = 0; i < n; i ++ )
    {
        while (j && s[i] != p[j])   j = ne[j - 1];
        if (s[i] == p[j])   j ++ ;
        if (j == m) printf("%d ", i - m + 1);
    }
}

int main()
{
    scanf("%d%s%d%s", &m, p, &n, s);
    
    getNext();
    KMP();
    
    return 0;
}

// 下标从1开始的写法
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
char p[N], s[N];
int ne[N], n, m;

void getNext()
{
    ne[0] = ne[1] = 0;
    for (int i = 2, j = 0; i <= m; i ++ )
    {
        while (j && p[i] != p[j + 1])   j = ne[j];
        if (p[i] == p[j + 1])   j ++ ;
        ne[i] = j;
    }
}

void KMP()
{
    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)
        {
            printf("%d ", i - m);
            j = ne[j];  // 这一句可以不用
        }
    }
}

int main()
{
    scanf("%d%s%d%s", &m, p + 1, &n, s + 1);
    
    getNext();
    KMP();
    
    return 0;
}
// 写法2 
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
char p[N], s[N];
int ne[N], f[N], n, m;

void getNext()
{
    ne[0] = ne[1] = 0;
    for (int i = 2, j = 0; i <= m; i ++ )
    {
        while (j && p[i] != p[j + 1])   j = ne[j];
        if (p[i] == p[j + 1])   j ++ ;
        ne[i] = j;
    }
}

void KMP()
{
    for (int i = 1, j = 0; i <= n; i ++ )
    {
        // j == m 可以不写
        while (j == m || (j && s[i] != p[j + 1]))   j = ne[j];
        if (s[i] == p[j + 1])   j ++ ;
        f[i] = j;
    }
    
    for (int i = 1; i <= n; i ++ )
        if (f[i] == m)
            printf("%d ", i - m);
}

int main()
{
    scanf("%d%s%d%s", &m, p + 1, &n, s + 1);
    
    getNext();
    KMP();
    
    return 0;
}
// 写法3
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
char p[N], s[N];
int ne[N], f[N], n, m;

void KMP()
{
    p[m + 1] = '#';		// 形成新的字符串:模式串 + '#' + 主串
    for (int i = 1, j = m + 2; i <= n; i ++ , j ++ )
        p[j] = s[i];
    
    ne[0] = ne[1] = 0;
    for (int i = 2, j = 0; i <= n + m + 1; i ++ )
    {
        while (j && p[i] != p[j + 1])   j = ne[j];
        if (p[i] == p[j + 1])   j ++ ;
        ne[i] = j;
    }
    
    for (int i = m + 2; i <= n + m + 1; i ++ )
        if (ne[i] == m)
            printf("%d ", i - 2 * m - 1);	// 本来要输出s串中的第i-m位,但是由于s串前面增加了m+1位,所以还得减去m+1, 即i-m-(m+1)=i-2*m-1
}

int main()
{
    scanf("%d%s%d%s", &m, p + 1, &n, s + 1);
    
    KMP();
    
    return 0;
}

Trie

AcWing835. Trie字符串统计

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int cnt[N], tr[N][26], idx;
char str[N];

void ins(char s[])
{
    int p = 0;
    for (int i = 0; s[i]; i ++ )
    {
        int u = s[i] - 'a';
        if (!tr[p][u])  tr[p][u] = ++ idx;
        p = tr[p][u];
    }
    cnt[p] ++ ;
}

int query(char s[])
{
    int p = 0;
    for (int i = 0; s[i]; i ++ )
    {
        int u = s[i] - 'a';
        if (!tr[p][u])  return 0;
        p = tr[p][u];
    }
    return cnt[p];
}

int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        char op[5];
        scanf("%s%s", op, str);
        if (*op == 'I') ins(str);
        else    printf("%d\n", query(str));
    }
    
    return 0;
}

AcWing143 最大异或对

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 31 * N;
int tr[M][2], idx;

void ins(int x)
{
    int p = 0;
    for (int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (!tr[p][u])  tr[p][u] = ++ idx;
        p = tr[p][u];
    }
}

int query(int x)	// 找到能与整数x异或得到最大值的"某个数"t
{
    int p = 0, t = 0;
    for (int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (tr[p][!u])
        {
            t = (t << 1) + !u;	// 也可以写成 t = t * 2 + !u
            p = tr[p][!u];
        }
        else
        {
            t = (t << 1) + u;	// 也可以写成 t = t * 2 + u
            p = tr[p][u];
        }
    }
    return t;
}

int main()
{
    int n, ans = 0;
    scanf("%d", &n);
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        ins(x);
        int t = query(x);
        ans = max(ans, x ^ t);
    }
    printf("%d\n", ans);
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 31 * N;
int tr[M][2], idx;

void ins(int x)
{
    int p = 0;
    for (int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (!tr[p][u])  tr[p][u] = ++ idx;
        p = tr[p][u];
    }
}

int query(int x)    // 这里的ans是两个数异或后得到的最大值
{
    int p = 0, ans = 0;
    for (int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (tr[p][u ^ 1])
        {
            ans += 1 << i;
            p = tr[p][u ^ 1];
        }
        else
        {
            ans += 0 << i;
            p = tr[p][u];
        }
    }
    return ans;
}

int main()
{
    int n, ans = 0;
    scanf("%d", &n);
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        ins(x);
        ans = max(ans, query(x));
    }
    printf("%d\n", ans);
    return 0;
}

并查集

AcWing836. 合并集合

传送门

// 写法1:路径压缩
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int p[N], n, m;

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

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )  p[i] = i;
    while (m -- )
    {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        a = find(a), b = find(b);
        if (*op == 'M')
        {
            if (a == b) continue;
            p[a] = b;
        }
        else    puts(a == b ? "Yes" : "No");
    }
    return 0;
}
// 写法2:启发式合并
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int p[N], sz[N], n, m;

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

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )  p[i] = i, sz[i] = 1; 
    while (m -- )
    {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        a = find(a), b = find(b);
        if (*op == 'M')
        {
            if (a == b) continue;
            if (sz[a] > sz[b])  swap(a, b);
            p[a] = b;
            sz[b] += sz[a];
        }
        else    puts(a == b ? "Yes" : "No");
    }
    return 0;
}
// 写法3:按深度合并
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int p[N], dep[N], n, m;

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

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )  p[i] = i; 
    while (m -- )
    {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        a = find(a), b = find(b);
        if (*op == 'M')
        {
            if (a == b) continue;
            if (dep[a] > dep[b])  swap(a, b);
            p[a] = b;
            if (dep[a] == dep[b])   dep[b] ++ ;
        }
        else    puts(a == b ? "Yes" : "No");
    }
    return 0;
}

AcWing837. 连通块中点的数量

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int p[N], sz[N], n, m;

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

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )  p[i] = i, sz[i] = 1;
    while (m -- )
    {
        string op;
        int a, b;
        cin >> op;
        if (op == "C")
        {
            scanf("%d%d", &a, &b);
            a = find(a), b = find(b);
            if (a == b) continue;
            p[a] = b;
            sz[b] += sz[a];
        }
        else if (op == "Q1")
        {
            scanf("%d%d", &a, &b);
            puts(find(a) == find(b) ? "Yes" : "No");
        }
        else
        {
            scanf("%d", &a);
            printf("%d\n", sz[find(a)]);
        }
    }
    
    return 0;
}

AcWing240. 食物链

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int p[N], dep[N], n, m, ans;

int find(int x)
{
    if (x != p[x])
    {
        int fa = find(p[x]);    // 找到x所在集合的代表元fa
        dep[x] = (dep[x] + dep[p[x]]) % 3;
        p[x] = fa;
    }
    return p[x];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )  p[i] = i, dep[i] = 0;
    while (m -- )
    {
        int c, x, y;
        scanf("%d%d%d", &c, &x, &y);
        // x或y大于n,或者是x吃y,并且x==y,即同类吃同类  则为假话
        if (x > n || y > n || (c == 2 && x == y))   ans ++ ;
        else
        {
            int a = find(x), b = find(y);
            // 如果集合号相同,说明x和y在同一个集合中,那么不需要合并
            if (a == b)
            {
                if ((dep[x] - dep[y] + 3) % 3 != c - 1)
                    ans ++ ;
            }
            // 否则说明集合号不同,说明x和y不在同一个集合中,那么就需要进行合并操作了
            else
            {
                p[a] = b;
                dep[a] = (dep[y] - dep[x] + 3 + c - 1) % 3;
            }
        }
    }
    
    printf("%d\n", ans);
    return 0;
}

AcWing838. 堆排序

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int q[N], n, m, len;

void down(int k)
{
    int mx = k;
    if (2 * k <= len && q[2 * k] < q[mx])   mx = 2 * k;
    if (2 * k + 1 <= len && q[2 * k + 1] < q[mx])   mx = 2 * k + 1;
    
    if (k != mx)
    {
        swap(q[k], q[mx]);
        down(mx);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )  scanf("%d", &q[i]);
    len = n;
    for (int i = n / 2; i >= 1; i -- )  down(i);
    
    while (m -- )
    {
        printf("%d ", q[1]);
        swap(q[1], q[len]);
        len -- ;
        down(1);
    }
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int q[N], n, m, len;

void up(int k)
{
    while (k > 1 && q[k] < q[k / 2])
    {
        swap(q[k], q[k / 2]);
        k /= 2;
    }
}

void down(int k)
{
    while (2 * k <= len)
    {
        int j = 2 * k;
        if (j + 1 <= len && q[j + 1] < q[j])    j ++ ;
        if (q[k] <= q[j])   break;
        swap(q[k], q[j]);
        k = j;
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        q[ ++ len] = x;
        up(len);
    }
    
    while (m -- )
    {
        printf("%d ", q[1]);
        swap(q[1], q[len]);
        len -- ;
        down(1);
    }
    
    return 0;
}

AcWing839 模拟堆

传送门

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
const int N = 100010;
// s[N]表示堆中某个位置上存放的是第几次插入 s存储(堆中数组下标为i的这个位置)里面存放的是(哪一次插入的数)
// ph[N]表示第几次插入的数在堆中的什么位置  ph存储的是(第k个插入的数)在(数组中的下标)
int q[N], s[N], pos[N], n, len;

void change(int a, int b)
{
    // 这三个swap的顺序没有要求
    swap(q[a], q[b]);   // 交换数组中的元素值,也就是交换堆中的结点的元素
    swap(s[a], s[b]);   // 交换两个结点所对应的是"第几次插入"
    swap(pos[s[a]], pos[s[b]]); // 交换第几次插入的两个结点的数组下标
}

void up(int k)
{
    while (k > 1 && q[k] < q[k / 2])
    {
        change(k, k / 2);
        k /= 2;
    }
}

void down(int k)
{
    int mx = k;
    if (k * 2 <= len && q[k * 2] < q[mx])	mx = k * 2;
    if (k * 2 + 1 <= len && q[k * 2 + 1] < q[mx])	mx = k * 2 + 1;
    if (mx != k)
    {
        change(k, mx);
        down(mx);    
    }
}

int main()
{
    int idx = 0;	// idx表示当前第几个插入,初始化为0,每次插入一个数后,idx++
    cin >> n;
    while (n--)
    {
        string op;
        int k, x;
        cin >> op;
        // 插入一个数x
        if (op == "I")
        {
            int x;
            cin >> x;
            ++ idx;
            q[ ++ len] = x;
            pos[idx] = len; // 表示第idx个插入的元素在堆中的位置是idx
            s[len] = idx;   // 表示堆数组中下标为len的那个位置的元素是第idx次插入的
            up(len);
        }
        // 输出当前集合中的最小值
        else if (op == "PM") {
            printf("%d\n", q[1]);   
        }
        // 删除当前集合中的最小值
        else if (op == "DM")
        {
            change(1, len);
            -- len;
            down(1);
        }
        // 删除第k个插入的数
        else if (op == "D")
        {
            cin >> k;
            int i = pos[k]; // 找到要删除的第k个数它所在堆中的数组下标
            change(i, len);
            -- len;
            up(i), down(i);
        }
        // 修改第k个插入的数,将其变为x
        else if (op == "C")
        { 
            cin >> k >> x;  // 表示修改第k个插入的数,将其改为x
            int i = pos[k]; // 找到第k个插入的数在堆中的位置数组下标为i
            q[i] = x;       // 将堆中i位置的里面的数组元素修改为x
            up(i), down(i);
        }
    }
    return 0;
}

哈希表

AcWing840. 模拟散列表

传送门

#include <bits/stdc++.h>
using namespace std;
const int P = 13331;
vector<int> c[P];

bool find(int x)
{
    int v = (x % P + P) % P, len = c[v].size();
    for (int i = 0; i < len; i ++ )
        if (c[v][i] == x)
            return true;
    return false;
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < P; i ++ )   c[i].clear();
    while (n -- )
    {
        int x;
        char op[5];
        scanf("%s%d", op, &x);
        if (*op == 'I') c[(x % P + P) % P].push_back(x);
        else    puts(find(x) ? "Yes" : "No");
    }
    return 0;
}

AcWing841. 字符串哈希

传送门

// 写法1:自然溢出
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, base = 131;
using ULL = unsigned long long ;
ULL h[N], c[N];
char s[N];
int n, m;

ULL get(int l, int r)
{
    return h[r] - h[l - 1] * c[r - l + 1];
}

int main()
{
    scanf("%d%d%s", &n, &m, s + 1);
    c[0] = 1;
    for (int i = 1; i <= n; i ++ )
    {
        c[i] = c[i - 1] * base;
        h[i] = h[i - 1] * base + s[i];
    }
    
    while (m -- )
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        if (get(l1, r1) == get(l2, r2)) puts("Yes");
        else    puts("No");
    }
    
    return 0;
}
// 写法二:单哈希
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, base = 131, P = 9999971;
int c[N], h[N], n, m;
char s[N];

int get(int l, int r)
{
    return (h[r] - 1ll * h[l - 1] * c[r - l + 1] % P + P) % P;
}

int main()
{
    scanf("%d%d%s", &n, &m, s + 1);
    c[0] = 1;
    for (int i = 1; i <= n; i ++ )
    {
        c[i] = c[i - 1] * base % P;
        h[i] = (h[i - 1] * base + s[i]) % P;
    }
    
    while (m -- )
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        if (get(l1, r1) == get(l2, r2)) puts("Yes");
        else    puts("No");
    }
    
    return 0;
}
// 写法3:双哈希
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, base1 = 131, base2 = 137, P1 = 13331, P2 = 9999971;
int c1[N], c2[N], h1[N], h2[N], n, m;
char s[N];

int get1(int l, int r)
{
    return (h1[r] - 1ll * h1[l - 1] * c1[r - l + 1] % P1 + P1) % P1;
}

int get2(int l, int r)
{
    return (h2[r] - 1ll * h2[l - 1] * c2[r - l + 1] % P2 + P2) % P2;
}

int main()
{
    scanf("%d%d%s", &n, &m, s + 1);
    c1[0] = c2[0] = 1;
    for (int i = 1; i <= n; i ++ )
    {
        c1[i] = c1[i - 1] * base1 % P1;
        h1[i] = (h1[i - 1] * base1 + s[i]) % P1;
        
        c2[i] = c2[i - 1] * base2 % P2;
        h2[i] = (h2[i - 1] * base2 + s[i]) % P2;
    }
    
    while (m -- )
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        if (get1(l1, r1) == get1(l2, r2) && get2(l1, r1) == get2(l2, r2))
            puts("Yes");
        else    puts("No");
    }
    
    return 0;
}

第三讲 搜索与图论

DFS

AcWing842 排列数字

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int path[N], n;
bool st[N];

void dfs(int u)
{
    if (u > n)
    {
        for (int i = 1; i <= n; i ++ )  printf("%d ", path[i]);
        puts("");
    }
    for (int i = 1; i <= n; i ++ )
    {
        if (!st[i])
        {
            path[u] = i;
            st[i] = true;
            
            dfs(u + 1);
            
            st[i] = false;
            path[u] = 0;
        }
    }
}

int main()
{
    scanf("%d", &n);
    dfs(1);
    return 0;
}
// 写法2:调用next_permutation函数
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int a[N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   a[i] = i + 1;
    do {
        for (int i = 0; i < n; i ++ )   printf("%d ", a[i]);
        puts("");
    } while (next_permutation(a, a + n));
    
    return 0;
}

AcWing843. n-皇后问题

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
bool col[N], dg[N], udg[N];
char g[N][N];
int n;

void dfs(int u)
{
    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[u - i + n])
        {
            g[u][i] = 'Q';
            col[i] = dg[u + i] = udg[u - i + n] = true;
            dfs(u + 1);
            col[i] = dg[u + i] = udg[u - i + n] = false;
            g[u][i] = '.';
        }
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            g[i][j] = '.';
    dfs(0);
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
bool row[N], col[N], dg[N], udg[N];
char g[N][N];
int n;

void dfs(int x, int y, int s)   // x表示行,y表示列,s表示当前皇后的个数
{
    if (y == n) y = 0, x ++ ;
    if (x == n) // 如果全部行已经扫描完了,那么就输出全部皇后所在的位置
    {
        if (s == n)
        {
            for (int i = 0; i < n; i ++ )   puts(g[i]);
            puts("");
        }
        return ;
    }
    
    // 如果(x,y)这个位置不放皇后,那么就递归它的下一个位置(x,y+1)
    dfs(x, y + 1, s);
    
    if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])
    {
        g[x][y] = 'Q';
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
        
        dfs(x, y + 1, s + 1);   // 递归下一个位置(x,y+1)的位置,因为之前放了皇后,所以s+1
        
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
        g[x][y] = '.';
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            g[i][j] = '.';
    dfs(0, 0, 0);
    return 0;
}

BFS

AcWing844. 走迷宫

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
typedef pair<int, int> PII;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int g[N][N], dist[N][N], n, m;
PII q[N * N];

int bfs()
{
    memset(dist, -1, sizeof dist);
    dist[0][0] = 0;
    int hh = 0, tt = 0;
    q[0] = {0, 0};
    
    while (hh <= tt)
    {
        PII t = q[hh ++ ];
        for (int i = 0; i < 4; i ++ )
        {
            int a =  + dx[i], b =  + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= m || ~dist[a][b] || g[a][b])
                continue;
            dist[a][b] = dist[][] + 1;
            q[ ++ tt] = {a, b};
        }
    }
    
    return dist[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]);
    printf("%d\n", bfs());
    return 0;
}

AcWing845. 八数码

传送门

#include <bits/stdc++.h>
using namespace std;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};

int bfs(string S)
{
    string T = "12345678x";
    queue<string> q;
    unordered_map<string, int> dist;
    dist[S] = 0;
    (S);
    
    while (())
    {
        string t = ();
        ();
        int d = dist[t];
        if (t == T) return d;
        int k = ('x');
        int x = k / 3, y = k % 3;
        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < 3 && b >= 0 && b < 3)
            {
                swap(t[k], t[a * 3 + b]);
                
                if (!(t))
                {
                    dist[t] = d + 1;
                    (t);
                }
                
                swap(t[k], t[a * 3 + b]);
            }
        }
    }
    
    return -1;
}

int main()
{
    string s;
    for (int i = 0; i < 9; i ++ )
    {
        char c;
        cin >> c;
        s += c;
    }
    printf("%d\n", bfs(s));
    return 0;
}

树与图的深度优先遍历

AcWing846. 树的重心

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], idx;
int n, ans = 1 << 30;

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

int dfs(int x, int fa)  // 返回以x为根的这棵子树的大小(包括x)
{
    int cnt = 0, s = 1;
    for (int i = h[x]; ~i; i = ne[i])
    {
        int y = e[i];
        if (y == fa )   continue;
        int t = dfs(y, x);
        cnt = max(cnt, t);
        s += t;
    }
    ans = min(ans, max(cnt, n - s));
    return s;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d", &n);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }
    dfs(1, -1);
    printf("%d\n", ans);
    return 0;
}
// 写法2:vector建图
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector<int> e[N];
int n, ans = 1 << 30;

int dfs(int x, int fa)  // 返回以x为根的这棵子树的大小(包括x)
{
    int cnt = 0, s = 1;
    for (auto &y : e[x])
    {
        if (y == fa)    continue;
        int t = dfs(y, x);
        cnt = max(cnt, t);
        s += t;
    }
    ans = min(ans, max(cnt, n - s));
    return s;
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        e[a].push_back(b), e[b].push_back(a);
    }
    dfs(1, -1);
    printf("%d\n", ans);
    return 0;
}

树与图的广度优先遍历

AcWing847. 图中点的层次

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N;
int h[N], e[M], ne[M], idx;
int dist[N], q[N], n, m;

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

int bfs()
{
    memset(dist, -1, sizeof dist);
    dist[1] = 0;
    int hh = 0, tt = 0;
    q[0] = 1;
    
    while (hh <= tt)
    {
        int u = q[hh ++ ];
        for (int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if (dist[v] == -1)
            {
                dist[v] = dist[u] + 1;
                q[ ++ tt] = v;
            }
        }
    }
    
    return dist[n];
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    printf("%d\n", bfs());
    return 0;
}

拓扑排序

AcWing848. 有向图的拓扑序列

传送门

// BFS求拓扑排序:add建图
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N;
int h[N], e[M], ne[M], idx;
int d[N], q[N], n, m;

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

bool topsort()
{
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ tt] = i;
    
    while (hh <= tt)
    {
        int u = q[hh ++ ];
        for (int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if (-- d[v] == 0)
                q[ ++ tt] = v;
        }
    }
    
    return tt == n - 1;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        d[b] ++ ;
    }
    if (topsort())
    {
        for (int i = 0; i < n; i ++ )   printf("%d ", q[i]);
        puts("");
    }
    else    puts("-1");
    return 0;
}

// BFS求拓扑排序:vector建图
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector<int> e[N];
int d[N], q[N], n, m;

bool topsort()
{
    int front = 1, rear = 0;
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ rear] = i;
    
    while (front <= rear)
    {
        int u = q[front ++ ];
        for (auto &v : e[u])
            if (-- d[v] == 0)
                q[ ++ rear] = v;
    }
    
    return rear == n;
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        e[a].push_back(b);
        d[b] ++ ;
    }
    if (topsort())
    {
        for (int i = 1; i <= n; i ++ )   printf("%d ", q[i]);
        puts("");
    }
    else    puts("-1");
    return 0;
}
// DFS求拓扑排序
/*
思路:DFS时候,遇到u->v边,通过在DFS函数快退出时将结点加入到答案ans中
实现v在序中列的位置始终在u的前面逆序输出即为所求的拓扑序列
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N;
int h[N], e[M], ne[M], idx;
int c[N], n, m;
vector<int> ans;

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

/*
c[u] = 1 表示之前已经访问过了,并且还递归访问过它的所有子孙(即dfs(u)曾经被调用过并已经返回)  过去
c[u] = -1 表示当前正在访问(即递归调用dfs(u)正在栈帧中,尚未返回)                           现在
c[u] = 0 表示从来没有访问过,未来或许会被访问到(从来没有调用过dfs(u)                       未来
*/
bool dfs(int u)
{
    c[u] = -1;	// 标记当前正在访问u
    for (int i = h[u]; ~i; i = ne[i])
    {
        int v = e[i];
        // 如果v正在被访问(则与u正在被访问矛盾)
        if (c[v] == -1)  return false;
        if (!c[v])
            if (!dfs(v))
                return false;
    }
    c[u] = 1;	// 标记已经访问过节点u了,对u的访问结束
    /*
    将节点u存入拓扑序列中, 可以理解为递归到了叶子节点后把这个叶子节点存入拓扑序列中,
    之后回溯时就会把叶子节点的父节点...根节点也都存入拓扑序列中, 因此到时候逆序输出就可以得到正确的拓扑序列了
    */
    ans.push_back(u);
    return true;
}

bool topsort()
{
    memset(c, 0, sizeof c);	// 初始化为都还没有被访问过
    for (int i = 1; i <= n; i ++ )
        if (!c[i])
            if (!dfs(i))
                return false;
    return true;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    if (topsort())
    {
        // 逆序输出dfs得到的结果,那么就是拓扑序列
        for (int i = () - 1; i >= 0; i -- ) printf("%d ", ans[i]);
        puts("");
    }
    else    puts("-1");
    return 0;
}

Dijkstra

AcWing849. Dijkstra求最短路 I

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N], dist[N], n, m;
bool st[N];

void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
               t = j;
        st[t] = true;
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    if (dist[n] == INF) puts("-1");
    else    printf("%d\n", 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);
    }
    dijkstra();
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
vector<pair<int, int>> e[N];
int dist[N], n, m;
bool st[N];

void dijkstra()
{
    memset(dist, 127, sizeof dist);
    dist[1] = 0;
    
    while (1)
    {
        int x = -1;
        for (int i = 1; i <= n; i ++ )
            if (!st[i] && (x == -1 || dist[i] < dist[x]))
                x = i;
        if (x == -1 || x == n)  break;
        st[x] = true;
        for (auto &it : e[x])
            dist[] = min(dist[], dist[x] + );
    }
    if (dist[n] < 1 << 30)  printf("%d\n", dist[n]);
    else    puts("-1");
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        e[a].push_back({b, c});
    }
    dijkstra();
    return 0;
}

AcWing850. Dijkstra求最短路 II

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, M = N, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int h[N], e[N], ne[N], w[N], idx;
int dist[N], n, m;
bool st[N];

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

void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue < PII, vector<PII>, greater<PII>> q;
    ({0, 1});
    
    while (())
    {
        auto t = ();   ();
        int u = ;
        if (u == n) break;
        if (st[u])  continue;
        st[u] = true;
        for (int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if (dist[v] > dist[u] + w[i])
            {
                dist[v] = dist[u] + w[i];
                ({dist[v], v});
            }
        }
    }
    if (dist[n] == INF) puts("-1");
    else    printf("%d\n", dist[n]);
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    dijkstra();
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef pair<int, int> PII;
vector<PII> e[N];
set<PII> q;
int dist[N], n, m;

void dijkstra()
{
    memset(dist, 127, sizeof dist);
    dist[1] = 0;
    ();
    for (int i = 1; i <= n; i ++ )
        ({dist[i], i});
    
    while (!())
    {
        int x = ()->second;
        if (x == n || dist[x] > 1 << 30)    break;
        (());
        for (auto &it : e[x])
        {
            if (dist[] > dist[x] + )
            {
                ({dist[], });
                dist[] = dist[x] + ;
                ({dist[], });
            }
        }
    }
    
    if (dist[n] < 1 << 30)  printf("%d\n", dist[n]);
    else    puts("-1");
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        e[a].push_back({b, c});
    }
    dijkstra();
    return 0;
}

bellman-ford

AcWing853. 有边数限制的最短路

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 510, M = 10010, INF = 0x3f3f3f3f;
int dist[N], backup[N], n, m, k;
struct Edge {
    int a, b, c;
} e[M];

void Bellman_Ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    while (k -- )
    {
        memcpy(backup, dist, sizeof dist);
        for (int i = 0; i < m; i ++ )
        {
            int a = e[i].a, b = e[i].b, c = e[i].c;
            dist[b] = min(dist[b], backup[a] + c);
        }
    }
    
    if (dist[n] > INF / 2)  puts("impossible");
    else    printf("%d\n", dist[n]);
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < m; i ++ )
        scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
        
    Bellman_Ford();
    
    return 0;
}

spfa

AcWing851. spfa求最短路

传送门

// 手写循环队列实现   也可以使用STL中的queue
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N, INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], w[M], idx;
int dist[N], q[N], n, m;
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 spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    int hh = 0, tt = 1;
    q[tt ++ ] = 1;
    st[1] = true;
    
    while (hh != tt)
    {
        int u = q[hh ++ ];
        if (hh == N)    hh = 0;
        st[u] = false;
        for (int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if (dist[v] > dist[u] + w[i])
            {
                dist[v] = dist[u] + w[i];
                if (!st[v])
                {
                    q[tt ++ ] = v;
                    if (tt == N)    tt = 0;
                    st[v] = true;
                }
            }
        }
    }
    if (dist[n] == INF) puts("impossible");
    else    printf("%d\n", dist[n]);
}

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

    return 0;
}

AcWing852. spfa判断负环

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 2010, M = 10010;
int h[N], e[M], ne[M], w[M], idx;
int dist[N], cnt[N], q[N], n, m;    // cnt记录1号顶点到图的某个顶点的最短路径中的边的个数
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()
{
    int hh = 0, tt = 1;
    for (int i = 1; i <= n; i ++ )
    {
        q[tt ++ ]= i;
        st[i] = true;
    }
    
    while (hh != tt)
    {
        int u = q[hh ++ ];
        if (hh == N)    hh = 0;
        st[u] = false;
        for (int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if (dist[v] > dist[u] + w[i])
            {
                dist[v] = dist[u] + w[i];
                cnt[v] = cnt[u] + 1;
                if (cnt[v] >= n)    return true;
                if (!st[v])
                {
                    q[tt ++ ] = v;
                    if (tt == N)    tt = 0;
                    st[v] = true;
                }
            }
        }
    }
    
    return false;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    
    puts(spfa() ? "Yes" : "No");
    
    return 0;
}

Floyd

AcWing854. Floyd求最短路

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
int dist[N][N], n, m, Q;

void Floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}

int main()
{
    scanf("%d%d%d", &n, &m, &Q);
    memset(dist, 0x3f, sizeof dist);
    for (int i = 1; i <= n; i ++ )  dist[i][i] = 0;
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        dist[a][b] = min(dist[a][b], c);
    }
    
    Floyd();
    
    while (Q -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        if (dist[a][b] > INF / 2)   puts("impossible");
        else    printf("%d\n", dist[a][b]);
    }
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 210;
int dist[N][N], n, m, Q;

void Floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                if (dist[i][k] < 1 << 30 && dist[k][j] < 1 << 30)
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}

int main()
{
    scanf("%d%d%d", &n, &m, &Q);
    memset(dist, 127, sizeof dist);
    for (int i = 1; i <= n; i ++ )  dist[i][i] = 0;
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        dist[a][b] = min(dist[a][b], c);
    }
    
    Floyd();
    
    while (Q -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        if (dist[a][b] < 1 << 30)   printf("%d\n", dist[a][b]);
        else    puts("impossible");
    }
    
    return 0;
}

Prim

AcWing858. Prim算法求最小生成树

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N], dist[N], n, m;
bool st[N];

int prim()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    int ans = 0;
    
    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        if (dist[t] == INF) return INF;
        ans += dist[t];
        st[t] = true;
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], g[t][j]);
    }
    return ans;
}

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] = g[b][a] = min(g[a][b], c);
    }
    
    int ans = prim();
    if (ans == INF) puts("impossible");
    else    printf("%d\n", ans);
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N], dist[N], n, m;
bool st[N];

int prim()
{
    memset(dist, 0x3f, sizeof dist);
    int ans = 0;
    
    for (int i = 0; i < n; i ++ )
    {
        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;
        if (i)  ans += dist[t];
        st[t] = true;
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], g[t][j]);
    }
    return ans;
}

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] = g[b][a] = min(g[a][b], c);
    }
    
    int ans = prim();
    if (ans == INF) puts("impossible");
    else    printf("%d\n", ans);
    
    return 0;
}
// 写法3
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
vector<pair<int, int>> e[N];
int dist[N], n, m;
bool st[N];

void prim()
{
    memset(dist, 127, sizeof dist);
    dist[1] = 0;
    int ans = 0, tot = 0;
    
    while (1)
    {
        int x = -1;
        for (int j = 1; j <= n; j ++ )
        {
            if (!st[j] && dist[j] < 1 << 30)
                if (x == -1 || dist[j] < dist[x])
                    x = j;
        }
        if (x == -1)    break;
        tot ++ ;
        st[x] = true;
        ans += dist[x];
        for (auto it : e[x])
            dist[] = min(dist[], );
    }
    
    if (tot != n)   printf("impossible");
    else    printf("%d\n", ans);
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        e[a].push_back({b, c});
        e[b].push_back({a, c});
    }
    prim();
    return 0;
}
// 写法4:堆优化版
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
typedef pair<int, int> PII;
vector<PII> e[N];
set<PII> q;
int dist[N], n, m;
bool st[N];

void prim()
{
    memset(dist, 127, sizeof dist);
    dist[1] = 0;
    ();
    for (int i = 1; i <= n; i ++ )
        ({dist[i], i});
    int ans = 0, tot = 0;
    
    while (!())
    {
        int x = ()->second;
        (());
        if (dist[x] > 1 << 30)  break;
        tot ++ ;
        ans += dist[x];
        st[x] = true;
        for (auto &it : e[x])
        {
            if (!st[] &&  < dist[])
            {
                ({dist[], });
                dist[] = ;
                ({dist[], });
            }
        }
    }
    
    if (tot != n)   puts("impossible");
    else    printf("%d\n", ans);
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        e[a].push_back({b, c});
        e[b].push_back({a, c});
    }
    prim();
    return 0;
}

Kruskal

AcWing859. Kruskal算法求最小生成树

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
struct Edge {
    int a, b, c;
    bool operator < (const Edge &t) const {
        return c < ;
    }
} e[M];
int p[N], n, m;

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

void Kruskal()
{
    sort(e + 1, e + 1 + m);
    for (int i = 1; i <= n; i ++ )  p[i] = i;
    int ans = 0, cnt = 0;   // cnt表示最小生成树中的边的总数
    for (int i = 1; i <= m; i ++ )
    {
        int a = find(e[i].a), b = find(e[i].b), c = e[i].c;
        if (a != b)
        {
            p[a] = b;
            ans += c;
            cnt ++ ;
        }
    }
    if (cnt < n - 1)    puts("impossible");
    else    printf("%d\n", ans);
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ )
        scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
    Kruskal();
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
struct Edge {
    int a, b, c;
    bool operator < (const Edge &t) const {
        return c < ;
    }
} e[M];
int p[N], n, m;

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

void Kruskal()
{
    sort(e + 1, e + 1 + m);
    for (int i = 1; i <= n; i ++ )  p[i] = i;
    int ans = 0, cnt = n;   
    for (int i = 1; i <= m; i ++ )
    {
        int a = find(e[i].a), b = find(e[i].b), c = e[i].c;
        if (a != b)
        {
            p[a] = b;
            ans += c;
            cnt -- ;
        }
    }
    if (cnt != 1)    puts("impossible");
    else    printf("%d\n", ans);
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ )
        scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
    Kruskal();
    return 0;
}

染色法判定二分图

AcWing860. 染色法判定二分图

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], idx;
int c[N], n, m;

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

bool dfs(int x)
{
    for (int i = h[x]; ~i; i = ne[i])
    {
        int y = e[i];
        if (!c[y])
        {
            c[y] = 3 - c[x];
            if (!dfs(y))    return false;
        } else if (c[x] == c[y])    return false;
    }
    return true;
}

bool check()
{
    for (int i = 1; i <= n; i ++ )
    {
        if (!c[i])
        {
            c[i] = 1;
            if (!dfs(i))    return false;
        }
    }
    return true;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }
    
    puts(check() ? "Yes" : "No");
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector<int> e[N];
int c[N], n, m;

bool dfs(int x)
{
    for (auto y : e[x])
    {
        if (!c[y])
        {
            c[y] = 3 - c[x];
            if (!dfs(y))
                return false;
        } else if (c[x] == c[y])    return false;
    }
    return true;
}

bool check()
{
    memset(c, 0, sizeof c);
    for (int i = 1; i <= n; i ++ )
    {
        if (!c[i])
        {
            c[i] = 1;
            if (!dfs(i))
                return false;
        }
    }
    return true;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ )
    {
        int x, y;
        scanf("%d%d", &x, &y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    if (check())    puts("Yes");
    else    puts("No");
    return 0;
}

匈牙利算法

AcWing861. 二分图的最大匹配

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 510, M = 1e5 + 10;
int h[N], e[M], ne[M], idx;
int n1, n2, m, v[N];
bool st[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; i = ne[i])
    {
        int y = e[i];
        if (!st[y])
        {
            st[y] = true;
            if (!v[y] || find(v[y]))
            {
                v[y] = x;
                return true;
            }
        }
    }
    return false;
}

void solve()
{
    int ans = 0;
    for (int i = 1; i <= n1; i ++ )
    {
        memset(st, 0, sizeof st);
        if (find(i))    ans ++ ;
    }
    printf("%d\n", ans);
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d%d", &n1, &n2, &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    solve();
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
vector<int> e[N];
int n1, n2, m, v[N];
bool st[N];

bool find(int x)
{
    st[x] = true;
    for (auto &y : e[x])
    {
        if (!v[y] || (!st[v[y]] && find(v[y])))
        {
            v[y] = x;
            return true;
        }
    }
    return false;
}

void solve()
{
    int ans = 0;
    for (int i = 1; i <= n1; i ++ )
    {
        memset(st, 0, sizeof st);
        if (find(i))    ans ++ ;
    }
    printf("%d\n", ans);
}

int main()
{
    scanf("%d%d%d", &n1, &n2, &m);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        e[a].push_back(b);
    }
    solve();
    return 0;
}

第四讲 数学知识

质数

AcWing866. 试除法判定质数

传送门

#include <bits/stdc++.h>
using namespace std;

bool check(int n)
{
    if (n < 2)  return false;
    
    for (int i = 2; i <= n / i; i ++ )
        if (n % i == 0)
            return false;
            
    return true;
}

int main()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int x;
        scanf("%d", &x);
        puts(check(x) ? "Yes" : "No");
    }
    return 0;
}

AcWing867. 分解质因数

传送门

#include <bits/stdc++.h>
using namespace std;

void solve(int n)
{
    for (int i = 2; i <= n / i; i ++ )
    {
        if (n % i == 0)
        {
            int s = 0;
            while (n % i == 0)
            {
                n /= i;
                s ++ ;
            }
            printf("%d %d\n", i, s);
        }
    }
    if (n > 1)  printf("%d 1\n", n);
    puts("");
}

int main()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int x;
        scanf("%d", &x);
        solve(x);
    }
    return 0;
}

AcWing868. 筛质数

传送门

// 埃式筛
// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int primes[N], cnt, n;
bool st[N];

void solve()
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            for (int j = 2 * i; j <= n; j += i)
                st[j] = true;
        }
    }
}

int main()
{
    scanf("%d", &n);
    solve();
    printf("%d\n", cnt);
    return 0;
}

// 写法2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int primes[N], cnt, n;
bool st[N];

void solve()
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            for (int j = i; j <= n / i; j ++ )
                st[i * j] = true;
        }
    }
}

int main()
{
    scanf("%d", &n);
    solve();
    printf("%d\n", cnt);
    return 0;
}
// 线性筛
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int primes[N], cnt, n;
bool st[N];

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

int main()
{
    scanf("%d", &n);
    solve();
    printf("%d\n", cnt);
    return 0;
}

约数

AcWing869. 试除法求约数

传送门

#include <bits/stdc++.h>
using namespace std;

void solve(int n)
{
    vector<int>  res;
    for (int i = 1; i <= n / i; i ++ )
    {
        if (n % i == 0)
        {
            res.push_back(i);
            if (i != n / i) res.push_back(n / i);
        }
    }
    
    sort((), ());
    for (auto &i : res)
        printf("%d ", i);
    puts("");
}

int main()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int x;
        scanf("%d", &x);
        solve(x);
    }
    return 0;
}

AcWing870. 约数个数

传送门

#include <bits/stdc++.h>
using namespace std;
const int P = 1e9 + 7;
using LL = long long ;
unordered_map<int, int> primes;

void solve(int n)   // 分解质因子
{
    for (int i = 2; i <= n / i; i ++ )
    {
        while (n % i == 0)
        {
            primes[i] ++ ;
            n /= i;
        }
    }
    if (n > 1)  primes[n] ++ ;
}

int main()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int x;
        scanf("%d", &x);
        solve(x);
    }
    
    LL ans = 1;
    for (auto &it : primes)
        ans = ans * ( + 1) % P;
    printf("%lld\n", ans);
    
    return 0;
}

AcWing871. 约数之和

传送门

#include <bits/stdc++.h>
using namespace std;
const int P = 1e9 + 7;
using LL = long long ;
unordered_map<int, int> primes;

void solve(int n)   // 分解质因子
{
    for (int i = 2; i <= n / i; i ++ )
    {
        while (n % i == 0)
        {
            primes[i] ++ ;
            n /= i;
        }
    }
    if (n > 1)  primes[n] ++ ;
}

int main()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int x;
        scanf("%d", &x);
        solve(x);
    }
    
    LL ans = 1;
    for (auto &it : primes)
    {
        // p是质因子,s是该质因子的个数
        LL p = , s = , t = 1;
        while (s -- )
            t = (t * p + 1) % P;
        ans = ans * t % P;
    }
    printf("%lld\n", ans);
    
    return 0;
}

AcWing872 最大公约数

传送门

#include <bits/stdc++.h>
using namespace std;

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

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

欧拉函数

AcWing873. 欧拉函数

传送门

#include <bits/stdc++.h>
using namespace std;

void solve(int n)
{
    int ans = n;
    for (int i = 2; i <= n / i; i ++ )
    {
        if (n % i == 0)
        {
            ans = ans / i * (i - 1);
            while (n % i == 0)  n /= i;
        }
    }
    if (n > 1)  ans = ans / n * (n - 1);
    
    printf("%d\n", ans);
}

int main()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int x;
        scanf("%d", &x);
        solve(x);
    }
    return 0;
}

Acwing874. 筛法求欧拉函数

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
using LL = long long ;
int phi[N], primes[N], cnt, n;
bool st[N];

void solve()
{
    phi[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; j < cnt && primes[j] <= n / i; j ++ )
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0)
            {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }
    
    LL ans = 0;
    for (int i = 1; i <= n; i ++ )  ans += phi[i];
    printf("%lld\n", ans);
}

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

快速幂

AcWing875. 快速幂

传送门

#include <bits/stdc++.h>
using namespace std;
using LL = long long ;

int qmi(LL a, int b, int p)
{
    LL  ans = 1;
    while (b)
    {
        if (b & 1)  ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

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

AcWing876. 快速幂求逆元

传送门

#include <bits/stdc++.h>
using namespace std;
using LL = long long ;

LL qmi(LL a, int b, int p)
{
    LL ans = 1;
    while (b)
    {
        if (b & 1)  ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

int main()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int a, p;
        scanf("%d%d", &a, &p);
        if (a % p == 0) puts("impossible");
        else    printf("%lld\n", qmi(a, p - 2, p));
    }
    return 0;
}

扩展欧几里得算法

AcWing877. 扩展欧几里得算法

传送门

#include <bits/stdc++.h>
using namespace std;

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()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int a, b, x, y;
        scanf("%d%d", &a, &b);
        exgcd(a, b, x, y);
        printf("%d %d\n", x, y);
    }
    return 0;
}

AcWing878. 线性同余方程

传送门

#include <bits/stdc++.h>
using namespace std;
using LL = long long ;

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()
{
    int m;
    scanf("%d", &m);
    while (m -- )
    {
        int a, b, m, x, y;
        scanf("%d%d%d", &a, &b, &m);
        int d = exgcd(a, m, x, y);
        if (b % d)  puts("impossible");
        else    printf("%lld\n", (LL) x * (b / d) % m);
    }
    return 0;
}

中国剩余定理

AcWing204. 表达整数的奇怪方式

传送门

这题其实是扩展中国剩余定理,而不是中国剩余定理。P1495才是中国剩余定理的模板题。

// P1495 中国剩余定理
#include <bits/stdc++.h>
using namespace std;
const int N = 15;
using LL = long long ;
LL a[N], m[N], M = 1;
int n;

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

// 中国剩余定理的模板
LL CRT() {
    LL ans = 0;
    for (int i = 1; i <= n; i ++ ) {
        LL Mi = M / m[i];
        LL x, y;
        exgcd(Mi, m[i], x, y);  // 利用扩展欧几里得求出x和y
        // 此时的x就是Mi模mi意义下的逆元
        ans = ((ans + a[i] * Mi * x) % M + M) % M;  // 保证最小正整数解
    }
	// 这里也可以写成reutnr ans  因为上面已经保证求出来的ans是最小正整数解了
    return (ans % M + M) % M;   // 保证最小正整数解
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) {
        // 这里m[i]就是题目中猪圈数,a[i]就是无家可归的猪数
        scanf("%lld%lld", &m[i], &a[i]);
        M *= m[i];  // 累乘每个式子中模数mi  得到M
    }
    printf("%lld\n", CRT());
    return 0;
}
// 本题,扩展中国剩余定理
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
using LL = long long ;
LL a[N], m[N], n;

// 注意数据范围可能会爆long long需要用到龟速乘
LL mul(LL a, LL b, LL mod) {
    LL ans = 0;
    while (b) {
        if (b & 1)  ans = (ans + a) % mod;
        a = (a + a) % mod;
        b >>= 1;
    }
    return ans % mod;
}

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

// 扩展中国剩余定理
LL EXCRT() {
    // ans表示前i-1个方程式的通解(余数), M为前i-1个方程式的模数的最小公倍数
    LL ans = a[1], M = m[1];
    for (int i = 2; i <= n; i ++ ) {
        LL t, y;
        // res其实就是(a2-a1)%m[i] 不过为了防止负数 需要这么写
        LL res = ((a[i] - ans) % m[i] + m[i]) % m[i];   
        // 使用扩欧求出最大公约数d,并且求出了整数t
        LL d = exgcd(M, m[i], t, y);
        // 无解
        if (res % d)    return -1;
        t = mul(t, res / d, m[i]);
        // 注意下面的这两个式子不能颠倒
        ans += t * M;       // 得到前i个方程的通解
        M = m[i] / d * M;   // M等于lcm(M,mi),注意乘法要在除法后面做,否则会爆long long
        // 让通解范围限定在0~(M-1)内,防止会出现负数
        ans = (ans % M + M) % M;    // 求出最小非负整数解
    }
    return (ans % M + M) % M;       // 求出最小非负整数解
}

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

高斯消元

AcWing883. 高斯消元解线性方程组

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const double eps = 1e-6;
double a[N][N];
int n;

int gauss()
{
    int r, c;   // r表示当前所在的行数,c表示当前所指向的列数
    for (r = 0, c = 0; c < n; c ++ ) {
        // step1: 找到当前列下绝对值最大的行t
        // 寻找绝对值最大的数是因为可以避免系数变得太大,精度较高.
        int t = r;
        for (int i = r; i < n; i ++ )
            if (fabs(a[t][c]) < fabs(a[i][c]))
                t = i;
        
        // 如果当前这一列的最大数都是0,那么所有数都是0,那么就跳过该列,继续枚举下一列
        if (fabs(a[t][c]) < eps)    continue;
        
        // 如果当前列下绝对值最大的数所在的行t和当前枚举的行r不同 那么执行step2
        // step2: 让第t行与未固定的行(第r行)的第一行交换
        if (t != r) {
            for (int j = c; j <= n; j ++ )
                swap(a[t][j], a[r][j]);
        }
        
        // step3: 将该行中的第一个非0的数化为1
        // 这里必须从后往前做,假设我们从前往后做,那么就先把该行中的第一个非0的数a[r][c]化为1了,此时a[r][c]=1
        // 但是它是一直循环的做,那么之后的数a[r][j]/=a[r][c]就等于a[r][j]/=1,也就等于a[r][j] 并没有改变
        // 主要的原因就是你从前往后的话就先把a[r][c]变为1了 那么后面的数除以的就是被更新后的a[r][c] 而不是最初的a[r][c]
        for (int j = n; j >= c; j -- )
            a[r][j] /= a[r][c];
        
        // step4: 将下面所有行的第c列变为0
        for (int i = r + 1; i < n; i ++ ) {
            // 只有当前行它的数不为0,才要进行削为0的操作
            // 如果当前行它的数为零,说明不用进行削为零的操作
            if (fabs(a[i][c]) > eps) {
                // 这里也需要倒着做 原理同上
                // 从后往前,当前i行的每个数字,都减去第i行的第c列a[i][c] * 第r行从第n列开始到第c列的每个数a[r][j]
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[i][c] * a[r][j];
            }
        }
        
        r ++ ;  // 这一行的工作做完,换下一行
    }
    
    // 说明剩下方程的个数是小于n的,说明不是唯一解,判断是无解还是无穷多解
    if (r < n) {
        // 因为已经是阶梯型,所以r~n-1的值应该都为0
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)    // 第i行的结果a[i][n]结果不是0 即出现了0 = 非0  所以无解
                return 2;
        return 1;   // 否则,0=0,就是r~n-1的方程都是多余方程    所以有无穷多组解
    }
    
    // 说明有唯一解  自底向上回代结果
    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[i][j] * a[j][n];
    
    return 0;
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j <= n; j ++ )
            scanf("%lf", &a[i][j]);
            
    int t = gauss();
    if (!t) {   // 有唯一解
        for (int i = 0; i < n; i ++ ) {
            // 可能会出现输出-0.00的情况 因此需要特判掉
            if (fabs(a[i][n]) < eps)    a[i][n] = 0;
            printf("%.2lf\n", a[i][n]);
        }
    } else if (t == 1) {    // 有无穷多组解
        puts("Infinite group solutions");
    } else  puts("No solution");    // 无解
    
    return 0;
}

AcWing884. 高斯消元解异或线性方程组

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N], n;

int gauss() {
    int r, c;
    for (r = 0, c = 0; c < n; c ++ ) {
        int t = r;
        for (int i = r; i < n; i ++ ) {
            if (a[i][c]) {
                t = i;
                break;
            }
        }
        
        if (!a[t][c])   continue;
        
        if (t != r) {
            for (int j = c; j <= n; j ++ )
                swap(a[t][j], a[r][j]);
        }
        
        for (int i = r + 1; i < n; i ++ ) {
            if (a[i][c]) {
                for (int j = n; j >= c; j -- )
                    a[i][j] ^= a[r][j]; // 也可以写成a[i][j] ^= a[i][c] & a[r][j]
            }
        }
        
        r ++;
    }
    
    if (r < n) {
        for (int i = r; i < n; i ++ )
            if (a[i][n])
                return 2;
        return 1;
    }
    
    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] ^= a[i][j] & a[j][n];
    return 0;
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n + 1; j++)
            scanf("%d", &a[i][j]);
            
    int t = gauss();
    if (!t) {
        for (int i = 0; i < n; i++)
            printf("%d\n", a[i][n]);
    }
    else if (t == 1)
        puts("Multiple sets of solutions");
    else
        puts("No solution");
    return 0;
}

求组合数

AcWing885. 求组合数 I

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 2010, mod = 1e9 + 7;
int C[N][N], T;

void init() {
    for (int i = 0; i < N; ++ i) {
        for (int j = 0; j <= i; ++ j) {
            if (j == 0 || j == i)   C[i][j] = 1;
            else    C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
        }
    }
}

int main()
{
    init();
    scanf("%d", &T);
    while (T -- ) {
        int n, m;
        scanf("%d%d", &n, &m);
        printf("%d\n", C[n][m]);
    }
    return 0;
}

AcWing886. 求组合数 II

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, P = 1e9 + 7;
using LL = long long ;
int fact[N], infact[N];
int T;

int qmi(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)  ans = (LL)ans * a % P;
        a = (LL)a * a % P;
        b >>= 1;
    }
    return ans;
}

void init() {
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; ++ i) {
        fact[i] = (LL)fact[i - 1] * i % P;
        infact[i] = (LL)infact[i - 1] * qmi(i, P - 2) % P;
    }
}

int main()
{
    init();
    scanf("%d", &T);
    while (T -- ) {
        int n, m;
        scanf("%d%d", &n, &m);
        printf("%d\n", (LL)fact[n] * infact[m] % P * infact[n - m] % P);
    }
    return 0;
}

AcWing887. 求组合数 III

传送门

#include <bits/stdc++.h>
using namespace std;
using LL = long long ;
int T, P;

int qmi(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)  ans = (LL)ans * a % P;
        a = (LL)a * a % P;
        b >>= 1;
    }
    return ans;
}

// 递推求解朴素的C(a,b)
int C(int n, int m) {
    if (m > n)  return 0;
    int ans = 1;
    // i控制的是分母的循环,对于b!,从1到b总共有b个数
    // j控制的是分子的循环,从a到a-b+1总共有b个数
    // 因此可以发现,分子和分母循环的次数是相同的
    for (int i = 1, j = n; i <= m; ++ i, -- j) {
        ans = (LL)ans * j % P;
        ans = (LL)ans * qmi(i, P - 2) % P;
    }
    return ans;
}

// 用Lucas定理递归求解
int Lucas(LL n, LL m) {
    if (n < P && m < P )    return C(n, m);
    return (LL)C(n % P, m % P) * Lucas(n / P, m / P) % P;
}

int main()
{
    scanf("%d", &T);
    while (T -- ) {
        LL n, m;
        scanf("%lld%lld%d", &n, &m, &P);
        printf("%d\n", Lucas(n, m));
    }
    return 0;
}

AcWing888. 求组合数 IV

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int s[N], primes[N], cnt;
bool st[N];

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

int get(int n, int p) {
    int ans = 0;
    while (n) {
        ans += n / p;
        n /= p;
    }
    return ans;
}

vector<int> mul(vector<int> &A, int b) {
    vector<int> C;
    for (int i = 0, t = 0; i < () || t; ++ i) {
        if (i < ())   t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    while (() > 1 && () == 0)   C.pop_back();
    return C;
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    get_primes(n);
    for (int i = 0; i < cnt; ++ i) {
        int p = primes[i];
        s[i] = get(n, p) - get(m, p) - get(n - m, p);
    }
    
    vector<int> ans;
    ans.push_back(1);
    for (int i = 0; i < cnt; ++ i)
        for (int j = 0; j < s[i]; ++ j)
            ans = mul(ans, primes[i]);
    
    for (int i = () - 1; i >= 0; -- i)
        printf("%d", ans[i]);
    puts("");
    
    return 0;
}

AcWing889. 满足条件的01序列

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int P = 1e9 + 7;
using LL = long long ;
int n;

int qmi(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1)  ans = (LL)ans * a % P;
        a = (LL)a * a % P;
        b >>= 1;
    }
    return ans;
}

int main() {
    scanf("%d", &n);
    int a = 2 * n, b = n, ans = 1;
    for (int i = 1, j = a; i <= n; ++ i, -- j) {
        ans = (LL)ans * j % P;
        ans = (LL)ans * qmi(i, P - 2) % P;
    }
    
    ans = (LL)ans * qmi(n + 1, P - 2) % P;
    printf("%lld\n", ans);
    
    return 0;
}
// 写法2
#include <bits/stdc++.h>
using namespace std;
using LL = long long ;
const int N = 2e5 + 10, P = 1e9 + 7;
int fact[N], infact[N], n;

int qmi(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = (LL)ans * a % P;
        a = (LL)a * a % P;
        b >>= 1;
    }
    return ans;
}

void init() {
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; ++ i) {
        fact[i] = (LL)fact[i - 1] * i % P;
        infact[i] = (LL)infact[i - 1] * qmi(i, P - 2) % P;
    }
}

int main() 
{
    init();
    scanf("%d", &n);
    int ans = (LL)fact[2 * n] * infact[n] % P * infact[n] % P * qmi(n + 1, P - 2) % P;
    printf("%d\n", ans);
    return 0;
}

容斥原理

AcWing890. 能被整除的数

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 20;
using LL = long long ;
int p[N], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i ++ )   scanf("%d", &p[i]);
    int ans = 0;
    for (int i = 1; i < 1 << m; i ++ ) {
        int t = 1, cnt = 0;
        for (int j = 0; j < m; j ++ ) {
            if (i >> j & 1) {
                if ((LL)t * p[j] > n) {
                    t = -1;
                    break;
                }
                cnt ++ ;
                t *= p[j];
            }
        }
        if (t == -1)    continue;
        cnt & 1 ? ans += n / t : ans -= n / t;
    }
    
    printf("%d\n", ans);
    return 0;
}

博弈论

AcWing891. Nim游戏

传送门

#include <bits/stdc++.h>
using namespace std;
using LL = long long ;

int main()
{
    int n, x;
    scanf("%d", &n);
    LL ans = 0;
    while (n -- )
    {
        scanf("%d", &x);
        ans ^= x;
    }
    
    puts(ans ? "Yes" : "No");
    
    return 0;
}

AcWing892. 台阶-Nim游戏

传送门

#include <bits/stdc++.h>
using namespace std;
using LL = long long ;

int main()
{
    int n, x;
    scanf("%d", &n);
    LL ans = 0;
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &x);
        if (i & 1)  ans ^= x;
    }
    
    puts(ans ? "Yes" : "No");
    
    return 0;
}

AcWing893. 集合-Nim游戏

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 10010;
int s[N], f[M], n, m;

int SG(int x) {
    if (~f[x])  return f[x];
    
    unordered_set<int> S;
    for (int i = 0; i < m; ++ i)
        if (x >= s[i])
            (SG(x - s[i]));
    
    for (int i = 0; ; ++ i)
        if (!(i))
            return f[x] = i;
}

int main()
{
    memset(f, -1, sizeof f);
    scanf("%d", &m);
    for (int i = 0; i < m; ++ i)    scanf("%d", &s[i]);
    scanf("%d", &n);
    int ans = 0;
    while (n -- ) {
        int x;
        scanf("%d", &x);
        ans ^= SG(x);
    }
    puts(ans ? "Yes" : "No");
    return 0;
}

AcWing894. 拆分-Nim游戏

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int f[N], n;

int SG(int x) {
    if (~f[x])  return f[x];
    
    unordered_set<int> S;
    for (int i = 0; i < x; ++ i)
        for (int j = 0; j <= i; ++ j)
            (SG(i) ^ SG(j));
    
    for (int i = 0; ; ++ i)
        if (!(i))
            return f[x] = i;
}

int main()
{
    memset(f, -1, sizeof f);
    scanf("%d", &n);
    int ans = 0;
    while (n -- ) {
        int x;
        scanf("%d", &x);
        ans ^= SG(x);
    }
    puts(ans ? "Yes" : "No");
    return 0;
}

第五讲 动态规划

背包问题

AcWing2. 01背包问题

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N], n, m;

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

AcWing3. 完全背包问题

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N], n, m;

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

AcWing4. 多重背包问题 I

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int f[N], n, m;

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

AcWing5. 多重背包问题 II

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
// 物品总个数 = N*logs = 1000*log200 = 24000
const int N = 24010, M = 2010;
int w[N], v[N], f[M], n, m, cnt;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) {
        int wi, vi, c;
        scanf("%d%d%d", &wi, &vi, &c);
        for (int k = 1; k <= c; k <<= 1) {
            w[ ++ cnt] = k * wi;
            v[cnt] = k * vi;
            c -= k;
        }
        if (c > 0) {
            w[ ++ cnt] = c * wi;
            v[cnt] = c * vi;
        }
    }
    
    n = cnt;
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= w[i]; j -- )
            f[j] = max(f[j], f[j - w[i]] + v[i]);
    
    printf("%d\n", f[m]);
    return 0;
}
// 写法二
#include <bits/stdc++.h>
using namespace std;
const int N = 24010, M = 2010;
int f[M], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) {
        int w, v, c;
        scanf("%d%d%d", &w, &v, &c);
        if (c * w >= m) {   // 转化完全背包
            for (int j = w; j <= m; j ++ )  
                f[j] = max(f[j], f[j - w] + v);
        } else {
            for (int k = 1; c; k <<= 1) {   // 进行二进制拆分 转化01背包
                int x = min(k, c);
                for (int j = m; j >= x * w; j -- )
                    f[j] = max(f[j], f[j - x * w] + x * v);
                c -= x;
            }
        }
    }
    
    printf("%d\n", f[m]);
    return 0;
}

AcWing9. 分组背包问题

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int w[N], v[N], f[N], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) {    // 物品组
        int c;
        scanf("%d", &c);
        for (int j = 1; j <= c; j ++ )  
            scanf("%d%d", &w[j], &v[j]);
        for (int j = m; j >= 1; j -- )  // 体积 也可以写成j >= 0
            for (int k = 0; k <= c; k ++ ) // 决策  选择第i组中的第k件物品
                if (j >= w[k])
                    f[j] = max(f[j], f[j - w[k]] + v[k]);
    }
    
    printf("%d\n", f[m]);
    return 0;
}

线性DP

AcWing898. 数字三角形

传送门

// 写法1
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int f[N][N], w[N][N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ )
            scanf("%d", &w[i][j]);
    // 初始化所有状态为负无穷(因为求最大值),即都是非法状态  
    memset(f, -0x3f, sizeof f); 
    // 因为起点(1,1)只能从点(0,0)或(0,1)到达,所以这两个状态是合法的,赋值为0
    f[0][0] = f[0][1] = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ )
            f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + w[i][j];
    
    int ans = -INF;
    for (int j = 1; j <= n; j ++ )
        ans = max(ans, f[n][j]);
    
    printf("%d\n", ans);
    return 0;
}
// 写法2:自顶向下
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int f[N][N], w[N][N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ )
            scanf("%d", &w[i][j]);
    
    // for (int i = 0; i <= n; i ++ )
    //     for (int j = 0; j <= i + 1; j ++ )
    //         f[i][j] = -INF;
    memset(f, -0x3f, sizeof f);
    // 对第一层做特殊处理,金字塔顶端就只有一个数,那么这个数本身就是最大值
    f[1][1] = w[1][1];
    for (int i = 2; i <= n; i ++ )  // 从金字塔第二层开始枚举到第n层
        for (int j = 1; j <= i; j ++ )
            f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + w[i][j];
    
    int ans = -INF;
    for (int j = 1; j <= n; j ++ )
        ans = max(ans, f[n][j]);
    
    printf("%d\n", ans);
    return 0;
}

// 写法2:自顶向下,空间优化
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int f[N], w[N][N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= i; j ++ )
            scanf("%d", &w[i][j]);
    
    memset(f, -0x3f, sizeof f);
    f[1] = w[1][1];
    for (int i = 2; i <= n; i ++ )  // 类比 01背包的优化
        for (int j = i; j >= 1; j -- )
            f[j] = max(f[j - 1], f[j]) + w[i][j];
    
    int ans = -INF;
    for (int j = 1; j <= n; j ++ )
        ans = max(ans, f[j]);
    
    printf("%d\n", ans);
    return 0;
}
// 写法3:自底向上
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int f[N][N], w[N][N], n;

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

// 写法3:自底向上,空间优化
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int f[N], w[N][N], n;

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

AcWing895. 最长上升子序列

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N], f[N], n;

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

AcWing896. 最长上升子序列 II

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
// d[]数组记录最长上升子序列 len表示最长上升子序列的长度
int a[N], d[N], n, len;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )  scanf("%d", &a[i]);
    for (int i = 1; i <= n; i ++ ) {
        // 直接把它接在最长上升子序列的后面
        if (!len || a[i] > d[len])  d[ ++ len] = a[i];
        // 题目说了严格递增  因此当a[i]等于d[]中的最后一个元素 则跳过 保证严格单调递增
        else if (a[i] == d[len])    continue;
        else {
            int l = 1, r = len;
            // 二分找到d数组中第一个>=a[i]的数d[l]
            while (l < r) {
                int mid = l + r >> 1;
                if (d[mid] >= a[i]) r = mid;
                else    l = mid + 1;
            }
            d[l] = a[i];    // 替换
        }
    }
    
    printf("%d\n", len);
    return 0;
}

AcWing897. 最长公共子序列

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
char a[N], b[N];
int f[N][N], n, m;

int main()
{
    scanf("%d%d%s%s", &n, &m, a + 1, b + 1);
    // 边界条件
    for (int j = 0; j <= m; j ++ )  f[0][j] = 0;
    for (int i = 0; i <= n; i ++ )  f[i][0] = 0;
    
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= m; j ++ ) {
            if (a[i] == b[j])   f[i][j] = f[i - 1][j - 1] + 1;
            else    f[i][j] = max(f[i - 1][j], f[i][j - 1]);
        }
    }
    
    printf("%d\n", f[n][m]);
    return 0;
}

AcWing902. 最短编辑距离

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
char a[N], b[N];
int f[N][N], n, m;

int main()
{
    scanf("%d%s%d%s", &n, a + 1, &m, b + 1);
    // 边界条件
    for (int j = 0; j <= m; j ++ )  f[0][j] = j;
    for (int i = 0; i <= n; i ++ )  f[i][0] = i;
    
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= m; j ++ ) {
            if (a[i] == b[j])   f[i][j] = f[i - 1][j - 1];
            else    f[i][j] = min(f[i - 1][j - 1], min(f[i - 1][j], f[i][j - 1])) + 1;
        }
    }
    
    printf("%d\n", f[n][m]);
    return 0;
}

AcWing899. 编辑距离

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 15, M = 1010;
char s[M][N];
int f[N][N];

int solve(char a[], char b[]) {
    int n = strlen(a + 1), m = strlen(b + 1);
    for (int j = 0; j <= m; j ++ )  f[0][j] = j;
    for (int i = 0; i <= n; i ++ )  f[i][0] = i;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= m; j ++ ) {
            if (a[i] == b[j])   f[i][j] = f[i - 1][j - 1];
            else    f[i][j] = min(f[i - 1][j - 1], min(f[i - 1][j], f[i][j - 1])) + 1;
        }
    }
    return f[n][m];
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )  scanf("%s", s[i] + 1);
    while (m -- ) {
        char t[N];
        int k, ans = 0;
        scanf("%s%d", t + 1, &k);
        for (int i = 1; i <= n; i ++ )
            if (solve(s[i], t) <= k)
                ans ++ ;
        printf("%d\n", ans);
    }
    
    return 0;
}

区间dp

AcWing282. 石子合并

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int s[N], f[N][N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) {
        scanf("%d", &s[i]);
        s[i] += s[i - 1];
    }
    
    // 边界条件    初始化
    memset(f, 127, sizeof f);   // 其他状态都是非法的  由于是求最小值 所以初始化为无穷大
    // 只有一堆 自身并不需要合并  最小花费为0
    for (int i = 1; i <= n; i ++ )  f[i][i] = 0;
    
    for (int len = 2; len <= n; len ++ ) {  // 枚举区间长度
        for (int l = 1; l + len - 1 <= n; l ++ ) {  // 枚举左端点l
            int r = l + len - 1;                    // 右端点r
            for (int k = l; k < r; k ++ )           // 枚举分界点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;
}

计数类DP

AcWing900. 整数划分

传送门

// 写法1:完全背包  二维
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
// f[i][j]表示从前i个数中选,总和恰好为j的划分方法的方案数
int f[N][N], n;

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

// 完全背包,一维
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int f[N], n;

int main()
{
    scanf("%d", &n);
    f[0] = 1;
    for (int i = 1; i <= n; i ++ ) 
        for (int j = i; j <= n; j ++ )
            f[j] = (f[j] + f[j - i]) % mod;
    
    printf("%d\n", f[n]);
    return 0;
}
// 计数dp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int f[N][N], n; // f[i][j]表示 总和是i,并且由j个数组成的方案数

int main()
{
    scanf("%d", &n);
    f[0][0] = 1;
    for (int i = 1; i <= n; i ++ )  // 枚举总和i  让总和从1枚举到n
        for (int j = 1; j <= i; j ++ )  // 枚举整数的个数  从1枚举到n个数
            f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;
    
    int ans = 0;
    for (int i = 1; i <= n; i ++ )  
        ans = (ans + f[n][i]) % mod;
    printf("%d\n", ans);
    
    return 0;
}

数位dp

AcWing338. 计数问题

传送门

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 15;
int nums[N], f[N][N];

int dfs(int pos, int cnt, int x, bool lead, bool limit) {
    if (!pos)   return cnt;
    
    if (!limit && !lead && ~f[pos][cnt])    return f[pos][cnt];
    
    int up = limit ? nums[pos] : 9, ans = 0;
    for (int i = 0, t; i <= up; i ++) {
        if (x != i )    t = cnt;
        else {
            if (lead && !x )    t = 0; 
            else    t = cnt + 1;
        }
        ans += dfs(pos - 1, t, x, lead && i == 0, limit && i == up);
    }
    
    if (!limit && !lead)    f[pos][cnt] = ans;
    return ans;
}

int solve(int x, int num)  {
    memset(f, -1, sizeof f);
    int n = 0;
    while (x) {
        nums[++ n] = x % 10;
        x /= 10;
    }
    return dfs(n, 0, num, true, true);
}

int main() 
{
    int l, r;
    while (cin >> l >> r, l || r)  {
        if (l > r) swap(l, r);
        for (int i = 0; i <= 9; i ++) {
            int ans = solve(r, i) - solve(l - 1, i);
            cout << ans << " ";
        }
        puts("");
    }
}

状压dp

AcWing291. 蒙德里安的梦想

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 12, M = 1 << N;
using LL = long long ;
LL f[N][M];
bool st[M];
int n, m;

int main()
{
    while (~scanf("%d%d", &n, &m), n || m) {
        for (int i = 0; i < 1 << n; i ++ ) {
            st[i] = true;
            int cnt = 0;
            for (int j = 0; j < n; j ++ ) {
                if (i >> j & 1) {
                    if (cnt & 1) {
                        st[i] = false;
                        break;
                    }
                } else  cnt ++ ;
            }
            if (cnt & 1)    st[i] = false;
        }
        
        memset(f, 0, sizeof f);
        f[0][0] = 1;
        for (int i = 1; i <= m; i ++ ) {
            for (int j = 0; j < 1 << n; j ++ ) {
                for (int k = 0; k < 1 << n; k ++ ) {
                    if (!(j & k) && st[j | k])
                        f[i][j] += f[i - 1][k];
                }
            }
        }
        
        printf("%lld\n", f[m][0]);
    }
    
    return 0;
}

AcWing91. 最短Hamilton路径

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 20;
// f[i][j]表示“点被经过的状态”对应的二进制数是i,且目前处于点j时的最短路径
int dist[N][N], f[1 << N][N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            scanf("%d", &dist[i][j]);
    
    memset(f, 127, sizeof f);
    f[1][0] = 0;    // 边界条件
    // i代表着是一个方案集合,其中每一个位置1和0,代表着这个点经过还是没有经过
    for (int i = 0; i < 1 << n; i ++ ) {
        for (int j = 0; j < n; j ++ ) { // 枚举当前到了哪一个点
            if (i >> j & 1) {   // 如果i集合中第j位是1,也就是当前经过了j点
                for (int k = 0; k < n; k ++ ) { // 枚举从哪个点k走到点j
                    // 如果k和j是同一个点,则跳过
                    if (k == j) continue;
                    // 写法1:(i ^ 1 << j) >> k & 1
                    // 写法2:(i & (~ (1 << j))) >> k & 1
                    // 写法3:(i - (1 << j)) >> k & 1
                    if ((i ^ 1 << j) >> k & 1) 
                        f[i][j] = min(f[i][j], f[i ^ 1 << j][k] + dist[k][j]);
                }
            }
        }
    }
    // 表示所有点都走过了,且终点是n-1的最短距离
    printf("%d\n", f[(1 << n) - 1][n - 1]);
    return 0;
}

树形dp

AcWing285. 没有上司的舞会

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 6010, M = N;
int h[N], e[M], ne[M], idx;
int happy[N], d[N], f[N][2], n;

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

void dfs(int u) {
    f[u][0] = 0, f[u][1] = happy[u];
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        dfs(v);
        f[u][0] += max(f[v][0], f[v][1]);
        f[u][1] += f[v][0];
    }
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )  scanf("%d", &happy[i]);
    for (int i = 0; i < n - 1; i ++ ) {
        int a, b;
        scanf("%d%d", &b, &a);
        d[b] ++ ;
        add(a, b);
    }
    
    int rt = 0;
    for (int i = 1; i <= n; i ++ )
        if (!d[i]) {
            rt = i;
            break;
        }
    
    dfs(rt);
    
    printf("%d\n", max(f[rt][0], f[rt][1]));
    return 0;
}

记忆化搜索

AcWing901. 滑雪

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int f[N][N], w[N][N], n, m;

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 a = x + dx[i], b = y + dy[i];
        if (a < 1 || a > n || b < 1 || b > m || w[a][b] >= w[x][y]) continue;
        v = max(v, dfs(a, b) + 1);
    }
    
    return v;
}

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

第六讲 贪心

区间问题

AcWing905. 区间选点

传送门

// 闭区间,按右端点从小到大排序
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Range {
    int l, r;
    bool operator < (const Range &t) const {
        return r < ; // 按右端点从小到大排序
    }
} range[N];
int n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   scanf("%d%d", &range[i].l, &range[i].r);
    sort(range, range + n);
    
    int ans = 0, lastR = -2e9;  // lastR 记录上一次已经选中的最右端点
    for (int i = 0; i < n; i ++ )
    {
        if (lastR < range[i].l)
        {
            ans ++ ;
            lastR = range[i].r;
        }
    }
    
    printf("%d\n", ans);
    return 0;
}
// 开区间,按左端点从大到小排序(其实就是上面写法的对称操作)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Range {
    int l, r;
    bool operator < (const Range& t) const {
        return l > ; // 按左端点从大到小排序
    }
} range[N];
int n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   scanf("%d%d", &range[i].l, &range[i].r);
    sort(range, range + n);
    // lastL记录上次已经选中的最左端点
    int res = 0, lastL = 2e9;
    for (int i = 0; i < n; i ++ )
    {
        if (lastL > range[i].r) 
        {
            res ++ ;
            lastL = range[i].l;
        }
    }
    printf("%d\n", res);
    return 0;
}

AcWing908. 最大不相交区间数量

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Range {
    int l, r;
    bool operator < (const Range &t) const {
        return r < ; // 按右端点从小到大排序
    }
} range[N];
int n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   scanf("%d%d", &range[i].l, &range[i].r);
    sort(range, range + n);
    
    int ans = 0, lastR = -2e9;  // lastR 记录上一次已经选中的最右端点
    for (int i = 0; i < n; i ++ )
    {
        if (lastR < range[i].l)
        {
            ans ++ ;
            lastR = range[i].r;
        }
    }
    
    printf("%d\n", ans);
    return 0;
}

AcWing906. 区间分组

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Range {
    int l, r;
    bool operator < (const Range &t) const {
        return l < ;
    }
} range[N];
int n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   scanf("%d%d", &range[i].l, &range[i].r);
    sort(range, range + n);
    
    priority_queue<int, vector<int>, greater<int>> q;
    for (int i = 0; i < n; i ++ )
    {
        if (() || range[i].l <= ())
            (range[i].r);
        else
        {
            ();
            (range[i].r);
        }
    }
    
    printf("%d\n", ());
    return 0;
}

AcWing907. 区间覆盖

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct Range {
    int l, r;
    bool operator < (const Range &t) const {
        return l < ;
    }
} range[N];
int n, S, T;

int main()
{
    scanf("%d%d%d", &S, &T, &n);
    for (int i = 0; i < n; i ++ )   scanf("%d%d", &range[i].l, &range[i].r);
    sort(range, range + n);
    
    bool ok = false;
    int ans = 0;
    for (int i = 0; i < n; i ++ )
    {
        int j = i, r = -2e9;
        while (j < n && range[j].l <= S)
        {
            r = max(r, range[j].r);
            j ++ ;
        }
        
        if (r < S)
        {
            ans = -1;
            break;
        }
        
        ans ++ ;
        
        if (r >= T)
        {
            ok = true;
            break;
        }
        
        S = r, i = j - 1;
    }
    
    if (ok) printf("%d\n", ans);
    else    puts("-1");
    
    return 0;
}

Huffman树

AcWing148. 合并果子

传送门

#include <bits/stdc++.h>
using namespace std;
int n;

int main()
{
    scanf("%d", &n);
    priority_queue<int, vector<int>, greater<int>> q;
    while (n -- ) {
        int x;
        scanf("%d", &x);
        (x);
    }
    
    int ans = 0;
    while (() > 1) {
        int x = ();    ();
        int y = ();    ();
        ans += x + y;
        (x + y);
    }
    
    printf("%d\n", ans);
    return 0;
}

排序不等式

AcWing913. 排队打水

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
using LL = long long;
int t[N], n;

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

绝对值不等式

AcWing104. 货仓选址

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )   scanf("%d", &a[i]);
    sort(a, a + n);
    
    int ans = 0;
    for (int i = 0; i < n; i ++ )   ans += abs(a[i] - a[n / 2]);
    
    printf("%d\n", ans);
    return 0;
}

推公式

AcWing125. 耍杂技的牛

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
using PII = pair<int, int> ;
PII cows[N];
int n;

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++) {
        int w, s;
        scanf("%d%d", &w, &s);
        cows[i] = {w + s, w};
    }
    sort(cows, cows + n);
    
    int sum = 0, ans = -2e9;
    for (int i = 0; i < n; i ++ ) {
        int w = cows[i].second, s = cows[i].first - w;
        ans = max(ans, sum - s);
        sum += w;
    }
    
    printf("%d\n", ans);
    return 0;
}