算法汇总

排序算法

计数排序

void countsort(int* a, int n)//计数排序
{
    for (int i = 1; i <= n; i++)
    {
        cnt[a[i]]++;
    }
    for (int j = 1; j <= Max; j++)
    {
        for (int k = 0; k < cnt[j]; k++)
            cout << j << " ";
    }
}

希尔排序

void ShellSort(int* arr, int n)
//shell排序,基本思想:有点类似于分组排序,数组下标为i的值与下标为i+shell(如果n为奇数,还存在与i+2*shelll进行比较的情况)的值进行比较,如果比他大就交换,反之就小,i从0遍历到n-shell(想想也知道只用遍历一半吧)
//然后shell自除以2,再重复上述操作。
//最后当shell为0时,数组便排列好了。
{
    int shell=n;
    while (shell > 0)
    {
        shell /= 2;//希尔增量,shell=shell/3+1也可以
        int i = 0;
        for (int i = 0; i < n - shell; i++)//折半,所以遍历原数组长度的一半就行
        {
            int end = i;
            int temp = arr[end + shell];
            while (end >= 0)
            {
                if (temp < arr[end])
                {
                    arr[end + shell] = arr[end];
                    end -= shell;//这个位置很关键,这样才能分别比较三项及以上的数,并且交换位置。
                }
                else
                    break;
            }
            arr[end+shell] = temp;
        }
    }
}

归并排序

void msort(int l,  int r)
//归并排序,基本思想:左端点指针与右端点指针,将其从中间分割,对左半部分进行递归,右半部分进行递归,
//然后从头分别遍历左半部分与右半部分,哪边小就把哪边的数追加到新的数组之中,如果左右半边数组有没有被遍历到的情况,则直接将后面的元素全部追加到新数组后面;
//最后通过新的已经排好的数组更新原来的数组,
//递归,后面由边界到抽象,逐渐合并为一个已经排好的数组。
{
    if (l == r)  return;
    int m = (l + r) / 2;
    msort(l, m);//对左半部分递归
    msort(m + 1, r);//对右半部分递归
    merge(l, m, r); //合并数组
}

void merge(int l, int m, int r)//合并数组
{
    int i = l, j = m + 1,k=l;
    while (i <= m && j <= r)//从头遍历数组的左半部分和右半部分,哪边小就把哪边的数组加到新数组中
    {
        if (arr[i] > arr[j])
            arr1[k++] = arr[j++];
        else
            arr1[k++] = arr[i++];
    }
    //后面是考虑左右半边数组有没被遍历到的情况,如果有就直接添加到新数组后面
    while (i <= m)
        arr1[k++] = arr[i++];
    while (j <= r)
        arr1[k++] = arr[j++];
    for (int i = l; i <= r; i++)//将已经排好的数组重新添加到原数组中
        arr[i] = arr1[i];
}

插入排序

void insertsort(int a[],int n)
//插入排序,基本思想:左半部分为有序区,右半部分为无序区,从有序区右端依次向左与无序区第一个值进行比较,
//如果比这个值大,则该值向右移,如果比这个值小,则插入这个值的右端,合并为新的有序区
{
    for (int i = 1; i < n; i++)
    {
        int now = a[i];
        int j;
        for (j = i - 1; j >= 0; j--)
        {
            if (now < a[j]) a[j + 1] = a[j];//若a[j]大于now,则往前移,直到不大于
            else
                break;
        }
        a[j + 1] = now;//因为j--,所以这里是j+1;
    }
}

快速排序

void quicksort(int a[], int l, int r)
//快速排序:基本思想为找到一个“哨兵”值,起始端和终止端各有一个指针i,j,
//如果指针i指向的值大于哨兵值,j指向的值小于哨兵值,则进行交换,直到左半部分的值全部小于哨兵值,右半部分的值全部大于哨兵值,然后对左右半部分进行递归,最后可以排序成功
{
    int i = l, j = r, tag = a[(l + r) / 2],temp;
    do {
        while (a[i] < tag) i++;
        while (a[j] > tag) j--;
        if (i <= j)
        {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
            i++; j--;//交换值,并且将两端指针各往前挪一格,方便之后继续进行交换操作
        }
    } while (i <= j);
    if (l < j) quicksort(a, l, j);//左半部分进行递归
    if (r > i) quicksort(a, i, r);//右半部分进行递归
}

基数排序

int maxbit(int data[], int n) 
{
    int maxData = data[0];             
    for (int i = 1; i < n; ++i)
    {
        if (maxData < data[i])
            maxData = data[i];
    }
    int d = 1;
    int p = 10;
    while (maxData >= p)
    {
        //p *= 10; // Maybe overflow
        maxData /= 10;
        ++d;
    }
    return d;
}
void radixsort(int data[], int n) 
{
    int d = maxbit(data, n);
    int *tmp = new int[n];
    int *count = new int[10]; 
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) 
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; 
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; 
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; 
        for(j = n - 1; j >= 0; j--) 
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++)
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete []tmp;
    delete []count;
}

堆排序

void max_heapify(int arr[], int start, int end) {
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end) {
        if (son + 1 <= end && arr[son] < arr[son + 1])
            son++;
        if (arr[dad] > arr[son])
            return;
        else {
            swap(arr[dad], arr[son]);
            dad = son;
            son = dad * 2 + 1;
        }
    }
}

void heap_sort(int arr[], int len) {
    for (int i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    for (int i = len - 1; i > 0; i--) {
        swap(arr[0], arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}

另外还有桶排序,这里就不列举了。

差分与前缀和

差分与前缀和是相对的,差分的本质就是前缀和的逆运算,相比前缀和要抽象一点,这里不详细介绍前缀和

对于一个一维数组a,其差分数组b,两者关系有:

b[n]=a[n]−a[n−1];b[n]=a[n]-a[n-1];b[n]=a[n]−a[n−1];

对于一个二维数组a,其差分矩阵b,两者关系有:

b[i][j]=a[i][j]−a[i][j−1]−a[i−1][j]+a[i−1][j−1];b[i][j] = a[i][j] - a[i][j-1] - a[i-1][j] + a[i-1][j-1];b[i][j]=a[i][j]−a[i][j−1]−a[i−1][j]+a[i−1][j−1];

实现An中从(x1,y1)(x1,y1)(x1,y1)(x2,y2)(x2,y2)(x2,y2)子矩阵同时加上或者减去常数C
通过对差分矩阵元素的的改变进而改变前缀和矩阵元素的值,代码实现形式:

b[x1][y1]+=c;b[x1][y1] += c;b[x1][y1]+=c;
b[x1][y2+1]−=c;b[x1][y2+1] -= c;b[x1][y2+1]−=c;
b[x2+1][y1]−=c;b[x2+1][y1] -= c;b[x2+1][y1]−=c;
b[x2+1][y2+1]+=c;b[x2+1][y2+1] += c;b[x2+1][y2+1]+=c;

对于一维数组a,其有差分数组b,若想在一维数组 a l 到 r 这个区间加上c,则对于差分数组b有:

b[l]+=c;b[l] += c;b[l]+=c;`
`b[r+1]−=c;b[r+1] -= c;b[r+1]−=c;

https://www.luogu.com.cn/problem/P3397

高精度算法

高精加

#include <iostream>
#include <tuple>
#include <algorithm>
#define LL long long
#define MAX 100000
using namespace std;
int a[MAX], b[MAX], c[MAX]{ 0 };
int main()
{
        string num1, num2;
        cin >> num1 >> num2;
        for (int i = num1.size()-1,j=0; i >=0 ; i--,j++)
            a[j] = num1[i] - '0';
            for (int i = num2.size()-1,j=0; i >=0 ; i--,j++)
            b[j] = num2[i] - '0';
        int max = num1.size() > num2.size() ? num1.size() : num2.size();
        for (int i = 0; i < max; i++)
        {
            c[i] += a[i] + b[i];
            c[i + 1] = c[i] / 10;
            c[i] %= 10;
        }
        max--;
        while (c[max+1])
        {
            max++;
        }
        for (int i = max; i >=0; i--)
            cout << c[i];
            return 0;
}

高精乘

#include <iostream>
#include <tuple>
#include <algorithm>
#define LL long long
#define MAX 100000
using namespace std;
int a[MAX], b[MAX], c[MAX]{ 0 };
int main()
{
            string num1, num2;
            cin >> num1 >> num2;
        for (int i = num1.size()-1,j=1; i >=0 ; i--,j++)
            a[j] = num1[i] - '0';
            for (int i = num2.size()-1,j=1; i >=0 ; i--,j++)
            b[j] = num2[i] - '0';
            int max = num1.size() > num2.size() ? num1.size() : num2.size();
        for (int i = 1; i <= num1.size(); i++)
        {
            for (int j = 1; j <= num2.size(); j++)
            {
                c[j + i - 1] += a[i] * b[j];
            }
        }
        int len = num1.size() + num2.size();
        for (int i = 1; i <= len; i++)
        {
            c[i + 1] += c[i] / 10;
            c[i] %= 10;
        }
        int cnt = 0;
        for (int i = 1; i <= len; i++)
        {
            if (c[i] == 0)
                cnt++;
        }
        if (cnt == len) cout << 0;
        else
        {
            while (!c[len]) len--;
            for (int i = len; i >= 1; i--)
                cout << c[i];
            }
        return 0;
}

枚举

二进制枚举

int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < (1<<n); i++)
    {
        for(int j = 0; j < n; j++) 
        {
            if(i & (1 << j))

               cout<<j;

        }
        cout<<endl;
    }
    return 0;
}

动态规划

背包dp

01背包

    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
                
                  
   for(int i = 1; i <= n; i++)
        for(int j = m; j >= v; j--)
            f[j] = max(f[j], f[j - v] + w);                                                                                                                                          
                                                              

完全背包

可以无限拿取背包内的东西

for(int i = 1 ; i <= n ; i++)
    for(int j = 0 ; j <= m ; j ++)
        {
           f[i][j] = f[i-1][j];
                 if(j-v[i]>=0)
           f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
        
         
for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }

多重背包

将背包拆出来,然后转化成01背包问题即可

       ll n,m;
    ll cnt=1;
    ll a,b,c;
    for(ll i=1;i<=n;i++)
    {
        cin>>a>>b>>c;
        for(ll j=1;j<=c;j++)
        {
            v[cnt]=a;
            w[cnt]=b;
            cnt++;
        }
    }
    for(ll i=1;i<=cnt;i++)
        for(ll j=m;j>=v[i];j--)
            f[j]=max(f[j],f[j-v[i]]+w[i]);

如果数据范围较大可能会导致超时,此时需要进行优化

二进制优化

如果不采用动态规划的做法, 就像普通的遍历问题那样, 是否采用二进制的计数方法对时间复杂度的优化没有任何关系。

 for (int i = 1; i <= n; i ++)
    {
        int a, b, s;
        int k = 1; 
        while (k <= s)
        {
            cnt ++;
            v[cnt] = a * k; 
            w[cnt] = b * k; 
            s -= k;
            k *= 2;  
        }
        if (s > 0)   
        {
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }

    n = cnt;  

混合背包

就是将上面三种混合起来,没什么好说的

for (循环物品种类) {
  if (是 0 - 1 背包)
     0 - 1 背包代码;
  else if (是完全背包)
    完全背包代码;
  else if (是多重背包)
    多重背包代码;
}

分组背包

for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j]; 
            for(int k=0;k<s[i];k++){
                if(j>=v[i][k])     f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);  
            }
        }
     }

单调队列优化

因为单调队列的单调性,所以我们可以用它来进行降低DP维数,进行时间和空间的优化。
这里以优化多重背包问题为例,
转移只会发生在「对当前物品体积取余相同」的状态之间。

            for (int j = 0; j < vi; j++) {
                int head = 0, tail = -1;
                for (int k = j; k <= C; k+=vi) {
                    dp[k] = g[k];
                    // 将不在窗口范围内的值弹出
                    if (head <= tail && q[head] < k - si * vi) head++;
                    // 如果队列中存在元素,直接使用队头来更新
                    if (head <= tail) dp[k] = max(dp[k], g[q[head]] + (k - q[head]) / vi * wi);
                    // 当前值比对尾值更优,队尾元素没有存在必要,队尾出队
                    while (head <= tail && g[q[tail]] - (q[tail] - j) / vi * wi <= g[k] - (k - j) / vi * wi) tail--;
                    // 将新下标入队 
                    q[++tail] = k;
                }
            }

区间dp

就是对区间进行动态规划
P1880 [NOI1995] 石子合并 - 洛谷

这题汇总了对于区间dp,求最大值,最小值以及形成一个圈的所有情况。
其中dp[j][j+i]dp[j][j+i]dp[j][j+i]代表,以j为起点,j+i为终点,区间的最优解情况
状态转移方程:

dp1[j][j+i]=min/max(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]−s[j−1]);dp1[j][j+i]=min/max(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]-s[j-1]);dp1[j][j+i]=min/max(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]−s[j−1]);

s[j+i]−s[j−1]s[j+i]-s[j-1]s[j+i]−s[j−1]代表j到j+i这个区间的所有元素的和,之所以要加上区间所有元素的和,是因为单纯将dp1[j][k],dp1[k+1][j+i]dp1[j][k],dp1[k+1][j+i]dp1[j][k],dp1[k+1][j+i]相加,其实少加了一次j到j+i这个区间的所有元素的。

#include <bits/stdc++.h>
using namespace std;
int dp1[501][501],dp2[501][501],n,a[501],s[501];
int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        a[n+i]=a[i];
    }
    memset(dp1,127,sizeof(dp1));
    memset(dp2,0,sizeof(dp2));
    n*=2;
    for(int i=1;i<=n;i++)
        s[i]=s[i-1]+a[i];
    for(int i=1;i<=n;i++){
        dp1[i][i]=0;
        dp2[i][i]=0;
    }
    for(int i=1;i<n;i++)
        for(int j=1;j<n-i;j++)
            for(int k=j;k<j+i;k++){
                dp1[j][j+i]=min(dp1[j][j+i],dp1[j][k]+dp1[k+1][j+i]+s[j+i]-s[j-1]);
                dp2[j][j+i]=max(dp2[j][j+i],dp2[j][k]+dp2[k+1][j+i]+s[j+i]-s[j-1]);
            }
    int ans1=1<<30,ans2=0;
    for(int i=1;i<=n;i++){
        ans1=min(ans1,dp1[i][i+n/2-1]);
        ans2=max(ans2,dp2[i][i+n/2-1]);
    }
    cout<<ans1<<endl;
    cout<<ans2<<endl;
    return 0;
}

树形dp

P1352 没有上司的舞会 - 洛谷

wls的板子,本题比起wls的题目,就是多了个·查找根节点的操作,其余的都是一样的,树形dp说白了就是在树结构在上进行dp,此时就需要遍历树(使用dfs或者bfs),wls的建链表(链式向前星)挺巧妙的,可以学学;

#include <bits/stdc++.h>
using namespace std;
const int MAX = 200005;
struct Node {
 Node* next;
 int Where;
}* f[MAX],a[MAX];
int v[MAX], n, l;
int vis[MAX];
long long dp[MAX][2];
inline void makelist(int x, int y) {//起点,终点
 a[++l].Where = y;
 a[l].next = f[x];
 f[x] = &a[l];
 vis[y] = 1;
} 
inline void solve(int d) {
 dp[d][1] = v[d];
 for (Node* x=f[d]; x; x = x->next) {
  solve(x->Where);
  dp[d][0] += max(dp[x->Where][0], dp[x->Where][1]);
  dp[d][1] += dp[x->Where][0];
 }
}
int main() {
 std::ios::sync_with_stdio(false);
 cin.tie(0);
 cin >> n;
 int res = 0;
 memset(f, 0, sizeof(f));
 for (int i = 1; i <= n; i++)
  cin >> v[i];
 for (int i = 1; i <= n - 1; i++) {
  int l,k; cin >> l>>k;
  makelist(k,l);
 }
 int root; //寻根,谁的入度为0谁就是根
 for (int i = 1; i <= n; i++) {
  if (!vis[i]) {
   root = i;
   break;
  }
 }
 solve(root);
 cout << max(dp[root][1],dp[root][0]);
 return 0;
}

状压dp

数位dp

搜索

DFS基本形式

/*
基本模型
void dfs(int step)
{
 if(所有空都填完了){
        判断最优解或者记录答案;
        return;
      {
    for(枚举所有的情况){
        if(该选项是合法的)
           记录下这个空;
           dfs(step+1);
           取消这个空
        }
}              
*/

DFS基本运用:N皇后问题

#include <iostream>
using namespace std;
const int N = 1000;
int n,res;
int a[N], b[N], c[N];//行,两个斜行,对于某一个点而言,
//其中一个斜行x+y固定,另一个行x-y固定;
void dfs(int step)
{
 if (step > n)
 {
  res++;
  return;
//因为棋子已经摆完了,所以回溯到上一种情况继续摆放。
 }
 
 for (int i = 1; i <= n; i++)
 {
  if (a[i] == 0 && b[step + i] == 0 && c[step - i + 20] == 0)
  {
   a[i] = 1; b[step + i] = 1; c[step - i + 20] = 1;
   dfs(step + 1);
   a[i] = 0; b[step + i] = 0; c[step - i + 20] = 0;
//如果没有满足上述情况的位置放置,则恢复现场。
  }
 }
}
int main()
{
 cin >> n;
 dfs(1);
 cout << res;
 return 0;
}

BFS基本形式

通常是与队列(queue)捆绑的。

/*
   Q.push(初始状态);
   while(!Q.empty())
   {
     State  u  =Q.front();
  Q.pop();
  for(枚举所有的可能状态)
     if(是合法的)
     Q.push(v);
   }
*/

BFS的基本应用:

P1443 马的遍历 - 洛谷

#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
int horse[8][2] = {
 {1,2},{2,1},{-1,2},{-2,1},{-1,-2},{-2,-1},{1,-2},{2,-1}
};
int ans[N][N];
queue<pair<int, int>> temp;
int main()
{
 memset(ans, -1, sizeof(ans));
 int n, m, dx, dy;
 cin >> n >> m >> dx >> dy;
 ans[dx][dy] = 0;
 temp.push({ dx,dy });
 while (!temp.empty()) 
 {
  pair<int, int> Top = temp.front();//标记原点
  temp.pop();
  for (int i = 0; i < 8; i++) {
   int dx1 = Top.first + horse[i][0];
   int dy1 = Top.second + horse[i][1];
   if (dx1 < 1 || dx1 > n || dy1 < 1 || dy1 > m || ans[dx1][dy1] != -1)
    continue;
   int temp1 = ans[Top.first][Top.second];//继承原点的步数
   ans[dx1][dy1] = temp1 + 1;
   temp.push({ dx1,dy1 });
  }
 }
 for (int i = 1; i <= n; i++)
 {
  for (int j = 1; j <= m; j++)
   printf("%-5d", ans[i][j]);
  printf("\n");
 }
 return 0;
}

字符串算法

这里就不写暴力匹配了。

KMP

const int N = 1e5;
int Next[N];
void getNext(string b,int next[]){//取模式串的next数组
    int len = b.size();
    next[0] = -1;
    int k = -1, j = 0;
    while (j < len - 1) {
        if (k == -1 || b[j] == b[k]) {
            k++;
            j++;
            if (b[j] != b[k]) next[j] = k;
            else
                next[j] = next[k];
        }
        else k = next[k];
    }
}

int kmp_search(string a , string b) {
    int i = 0, j = 0;
    getNext(b, Next);
    while (i < a.size()&&j<b.size()) {
        if (j==-1||a[i] == b[j]) i++, j++;
        else  j = Next[j];//递归思想,将模式串下标返回Next[j]
    }
    if (j == b.size()) 
        return i - j;
    else
        return -1;
}

EXkmp

next[i]: T[i]...T[m−1] next[i]: T[i]...T[m - 1]next[i]: T[i]...T[m−1]与 T(模式串) 的最长相同前缀长度;

extend[i]: S[i]...S[n−1]extend[i]: S[i]...S[n - 1]extend[i]: S[i]...S[n−1] 与 T 的最长相同前缀长度。

void GetNext(string T, int m, int next[])
{
    int a = 0, p = 0;
    next[0] = m;
    for (int i = 1; i < m; i++)
    {
        if (i >= p || i + next[i - a] >= p)
        {
            if (i >= p)
                p = i;
            while (p < m && T[p] == T[p - i])
                p++;
            next[i] = p - i;
            a = i;
        }
        else
            next[i] = next[i - a];
    }
}
void GetExtend(string S, int n, string T, int m, int extend[], int next[])
{
    int a = 0, p = 0;
    GetNext(T, m, next);

    for (int i = 0; i < n; i++)
    {
        if (i >= p || i + next[i - a] >= p) 
        {
            if (i >= p)
                p = i;
            while (p < n && p - i < m && S[p] == T[p - i])
                p++;
            extend[i] = p - i;
            a = i;
        }
        else
            extend[i] = next[i - a];
    }
}

BM算法

bm可以看作进阶的kmp算法,效率相比kmp要高。
BM算法定义了两个规则:

坏字符规则:当文本串中的某个字符跟模式串的某个字符不匹配时,我们称文本串中的这个失配字符为坏字符,此时模式串需要向右移动,移动的位数 = 坏字符在模式串中的位置 - 坏字符在模式串中最右出现的位置。此外,如果"坏字符"不包含在模式串之中,则最右出现位置为-1。

好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。

const int N = 1e4;
int BC[N],suffix[N],gs[N];
void getBC(string pattern, int bc[]) {//BC表
    for (int i = 0; i < 256; i++)
        bc[i] = -1;
    for (int i = 0; i < pattern.size(); i++)
        bc[pattern[i]] = i;
}

void suffixes(string a, int suffix[]) {//构建suffix表,suffix[i] = s 表示以i为边界,与模式串后缀匹配的最大长度。
    int len = a.size(),num;
    suffix[len - 1] = len;
    for (int i = len - 2; i >= 0; i--) {
        for (num = 0; num <= i && a[i - num] == a[len - num - 1]; num++)
            suffix[i] = num;
    }
}

void getGS(string a, int gs[]) {//构建gs表,记录了每次需要移动的距离。
    int len = a.size(), lastindex = len - 1;
    suffixes(a, suffix);
    for (int i = 0; i < len; i++)
        gs[i] = len;
    for (int i = lastindex; i >= 0; i--) {
        if (suffix[i] == i + 1) {
            for (int j = 0; j < lastindex - i; j++) {
                if (gs[j] == len)
                    gs[j] = lastindex - i;
            }
        }
    }
    for (int i = 0; i < lastindex; i++) {
        gs[lastindex - suffix[i]] = lastindex - i;
    }
}

int BMsearch(string a, string b ) {
    memset(BC, 0, N);
    memset(gs, 0, N);
    getBC(b,BC);
    getGS(b,gs);
    int ptr1 = 0, ptr2=0;
    int len1 = a.size(), len2 = b.size(), maxIndex = b.size() - 1;
    while (ptr1 + len2 <= len1) {
        for (ptr2 = maxIndex; ptr2 >= 0 && b[ptr2] == a[ptr1 + ptr2]; ptr2--);
        if (ptr2 == -1) break;
        else {
            ptr1 += max(gs[ptr2], ptr2 - BC[a[ptr1 + ptr2]]);//好后缀和怀字符规则,取移动数目较大的。
        }
    }
    return (ptr1 + len2 <= len1) ? ptr1 : -1;
}

最长回文子串

Manacher算法

查找一个字符串的最长回文子串,这里使用了string,推荐还是用char数组,string操作比较耗时。

string preProcess(string str) {//准备字符串
 int len = str.size();
 if (len == 0) return "^$";
 string start = "^";
 for (int i = 0; i < len; i++)
  start =start + "#" + str[i];
 start += "#$";
 return start;
}

string manacher(string str) {//马拉车算法
string str1 = preProcess(str);
 int len = str1.size();
 int* arr = new int[len];
 int C = 0, R = 0;
 for (int i = 1; i < len - 1; i++) {
  int i_mirror = 2 * C - i;
  if (R > i) arr[i] = min(R - i, arr[i_mirror]);//防止超出R
  else arr[i] = 0;//R=i的情况
  while (str1[i + 1 + arr[i]] == str1[i - 1 - arr[i]])                       arr[i]++;//中心拓展
  if (i + arr[i] > R) {//更新边界
   C = i;
   R = i + arr[i];
  }
 }
 int Max = 0, center = 0;
 for (int i = 1; i < len - 1; i++) {
  if (arr[i] > Max) {
   Max = arr[i];
   center = i;
  }
 }
 int start = (center - Max) / 2;
 return str.substr(start,  Max);
}

java代码,没事写一个

class Solution {
     public static String preString(String str){
        int len=str.length();
        if(len==0) return "^&";
        String start="^";
        for(int i=0;i<len;i++)
            start=start+"#"+str.charAt(i);
        start+="#&";
        return start;
    }
    public static String manacher(String str){
        String str1=preString(str);
        int len=str1.length();
        int[] arr=new int[len];
        int C=0,R=0;
        for(int i=1;i<len-1;i++){
            int i_mirror=2*C-i;
           if(R>i) arr[i]=Math.min(arr[i_mirror],R-i);
           else arr[i]=0;
           while(str1.charAt(i+1+arr[i])==str1.charAt(i-1-arr[i]))
               arr[i]++;
           if(arr[i]+i>R){
               C=i;
               R=i+arr[i];
           }
        }
        int Max=0,center=0;
        for(int i=1;i<len-1;i++){
            if(arr[i]>Max) {
                Max = arr[i];
                center = i;
            }
        }
        int beg=(center-Max)/2;
        return str.substring(beg,beg+Max);
    }
    public String longestPalindrome(String s) {
            return manacher(s);
    }
}

dp实现

本质上就是枚举边界和长度,然后更新一下dp数组就好了

       public static String dpSolve(String str){
            int maxlen=1,start=0;
            int len=str.length();
            boolean[][] dp=new boolean[len][len];
            for(int i=0;i<len;i++)
                dp[i][i]=true;
            for(int length=2;length<=len;length++) {
                for (int i = 0; i < len; i++) {
                    int j = length + i - 1;
                    if(j>=len)
                        break;
                    if (str.charAt(i) != str.charAt(j))
                        dp[i][j] = false;
                    else {
                        if (j - i < 3)
                            dp[i][j] = true;
                        else
                            dp[i][j] = dp[i + 1][j - 1];
                    }
                    if (dp[i][j] && j - i + 1 > maxlen) {
                        maxlen = j - i + 1;
                        start = i;
                    }
                }
            }
            return str.substring(start,start+maxlen);
    }

数论

埃氏筛

就是从质数2开始,其整数倍均不为质数,将区间内所有的2的倍数的下标,标记为false,代表不为质数,再从第二个质数开始,将其倍数标记为false,以此循环,最后留下来的都是质数。

const int MAX = 1e5+1;
int isPrime[MAX];
int Prime[MAX];
void solve(int n) {
 int n,ptr=0; cin >> n;
 for (int i = 0; i <= n; i++)
  isPrime[i] = 1;
 isPrime[0] = isPrime[1] = false;
 for (int i = 2; i <= n; i++) {
  if (isPrime[i]) {
   Prime[ptr++] = i;
   for (int j = 2 * i; j <= n; j += i)
    isPrime[j] = 0;
  }
 }
 for (int i = 0; i < ptr; i++)
  cout << Prime[i] << endl;
}

欧拉筛

核心思想是让每一个合数被其最小质因数筛到
关键就在于 if(i%isprime[j]==0)

#include <iostream>
using namespace std;
const int Max = 1e8 + 5;
int n, numPrime;
int prime[Max];
bool isPrime[Max];
int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for (int i = 2; i <= n; i++) {
        if (!isPrime[i])
            prime[numPrime++] = i;
        for (int j = 0; j < numPrime; j++) {
            if (i * prime[j] > n)
                break;
            isPrime[i * prime[j]] = true;
            if (i % prime[j] == 0)
                break;
        }
    }
    cout << numPrime;
    return 0;
}

快速幂

快速幂本质上就是一种分治算法,有递归和迭代两个版本

递归

 double quickMul(double x, long long N) {
        if (N == 0) {
            return 1.0;
        }
        double y = quickMul(x, N / 2);
        return N % 2 == 0 ? y * y : y * y * x;
    }

迭代

 double quickMul(double x, long long N) {
        double ans = 1.0;
        // 贡献的初始值为 x
        double x_contribute = x;
        // 在对 N 进行二进制拆分的同时计算答案
        while (N > 0) {
            if (N % 2 == 1) {
                // 如果 N 二进制表示的最低位为 1,那么需要计入贡献
                ans *= x_contribute;
            }
            // 将贡献不断地平方
            x_contribute *= x_contribute;
            // 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
            N /= 2;
        }
        return ans;
    }

数据结构

单调栈

保证栈中元素为单调递减或者递增的数据结构

stack<int> S;
 int T; cin >> T;
 while (T--) {
  int x; cin >> x;
  while (!S.empty() && S.top() > x)
   S.pop();
  S.push(x);
 }
 while (!S.empty()) {
  int temp = S.top();
  S.pop();
  cout << temp<<" ";
 }

模拟单调栈

typedef unsigned long long ull;
ull n,top,ans,c[2000001],s[2000001];
//top 栈首
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%llu",&c[i]);
    for(int i=1;i<=n;i++){
        while(top&&c[i]>=c[s[top]]){
            ans^=s[top];
            top--;
        }
        ans^=i;
        s[++top]=i;
        printf("%llu\n",ans);
    }
    return 0;
}

单调队列

保证队列中的元素单调递增或者单调递减

deque<int> Q; 
for (int i = 0; i < n; ++i)
{
    if (!Q.empty() && i - Q.front() >= m)
        Q.pop_front();
    while (!Q.empty() && V[Q.back()] < V[i])
        Q.pop_back();
    Q.push_back(i); 
    if (i >= m - 1)
        cout << V[Q.front()] << " ";
}

模拟单调队列

ll a[MAX],q[MAX],front=1,rear=0;//front队首,rear队尾 
    for(int i=1;i<=n;i++)
    {
  while(front<=rear&&a[q[rear]]>=a[i])
   rear--;
  q[++rear]=i;
  if(q[front]<i-k+1)
   front++;
  if(i>=k)
  cout<<a[q[front]]<<" ";
 }
 cout<<endl;
 
 for(int i=1;i<=n;i++){
  while(front<=rear&&a[q[rear]]<=a[i])
   rear--;
  q[++rear]=i;
  if(q[front]<i-k+1)
   front++;
  if(i>=k)
  cout<<a[q[front]]<<" ";
 }

字典树

数组存储

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//Trie
const int MAX = 1e6 + 10;
const int charsize = 27;


//记录此节点的子节点编号(默认为小写字母)
int nxt[MAX][charsize];

//是否为终止节点
bool isend[MAX];

//cnt保存当前节点编号数
int root = 0, cnt = 0;

void insert(char s[], int len) {
 int now = 0;
 for (int i = 1; i <= len; i++) {
  int x = s[i] - 1;
  if (!nxt[now][x])
   nxt[now][x] = ++cnt;
  now = nxt[now][x];
 }
 isend[now] = 1;
}

bool search(char s[], int len) {
 int now = 0;
 for (int i = 1; i <= len; i++) {
  int x = s[i] - 'a';
  if (!nxt[now][x])
   return false;
  now = nxt[now][x];
 }
 return isend[now];
}


int main() {
 std::ios::sync_with_stdio(false);
 cin.tie(0); cout.tie(0);
 return 0;
}

结构体写法

//正常
struct TreeNode1 {
 int nxt[charsize];
 bool isend;
}tree1[MAX];

//字符集较大
struct TreeNode2 {
 unordered_map<char, int> nxt_map;
 bool isend;
}tree2[MAX];

//指针写法
struct TreeNode3 {
 TreeNode3* nxt[charsize];
 bool isend;
}tree3[MAX];//insert search的操作思路类似,不写了。

统计前缀

把isend数组改成tol数组记录一下以某个位置为重点的字符串为前缀的数量就可以了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int charsize = 70;
const int MAXN = 3e6 + 10;
int nxt[MAXN][charsize],root,cnt;
int tol[MAXN];

int getnum(char x) {
 if (x >= 'A' && x <= 'Z')
  return x - 'A';
 else if (x >= 'a' && x <= 'z')
  return x - 'a' + 26;
 else
  return x - '0' + 52;
}

void init(int n) {
 for (int i = 0; i < n; i++) {
  for (int j = 0; j < charsize; j++) {
   nxt[i][j] = 0;
  }
 }

 for (int i = 0; i < n; i++)
  tol[i] = 0;
}
int main() {
 std::ios::sync_with_stdio(false);
 cin.tie(0); cout.tie(0);
 int T; cin >> T;
 while (T--) {
  init(cnt + 1);
  root = 0, cnt = 0;
  int n, q; cin >> n >> q;
  while(n--){
   string s; cin >> s;
   int now = 0;
   for (int i = 0; i < s.size(); i++) {
    int x = getnum(s[i]);
    if (!nxt[now][x])
     nxt[now][x] = ++cnt;
    now = nxt[now][x];
    tol[now]++;
   }
  }
  while(q--) {
   string pat; cin >> pat;
   int now = 0,flag=1;
   for (int i = 0; i < pat.size(); i++) {
    int x = getnum(pat[i]);
    if (!nxt[now][x]) {
     flag = 0;
     cout << 0 << endl;
     break;
    }
    now = nxt[now][x];
   }
   if(flag)
   cout << tol[now] << endl;
  }
 }
 return 0;
}

链表

单链

struct link
{
 int data;
 link* next;
 link* last;
} ;

link* creatlink()//创建一个头结点,链表的起始位置。
{
 link* headNode = (link*)malloc(sizeof(link));
 if (headNode == NULL)
  exit(1);
 headNode->next = NULL;
 headNode->last = NULL;
 return headNode;
}

link* creatNode(int data)//创建一个结点,后续将其插入链表。
{
 link* NewNode = (link*)malloc(sizeof(link));
 if (NewNode == NULL)
  exit(1);
 NewNode->data = data;
 NewNode->next = NULL;
 NewNode->last = NULL;
 return NewNode;
}

 void addNode(link * headNode,int data)//为该链表添加一个结点
{
 link* AddNode = creatNode(data); 
 AddNode->next = headNode->next;
 headNode->next = AddNode;
}

 void delNode(link* headNode, int num)//删除某个节点
 {
  link* pmove = headNode->next;
  link* p1move = headNode;         if(p1move->data==num){            headNode->next=NULL;            headNode=pmove;                    }
  while (pmove)
  { 
   if (pmove->data == num)
   {
    p1move->next = pmove->next;
   }
   pmove=pmove->next;
   p1move=p1move->next;
  }
 }

 void insertNode(link* headNode, int num,int data) {
  link* pmove = headNode->next;
  link* p1move = headNode;
  link* node = creatNode(data);
  while (pmove)
  {
   if (pmove->data == num)
   {
    node->next = pmove->next;
    pmove->next = node;
    break;
   }
   pmove = pmove->next;
   p1move = p1move->next;
  }
 }
int  printflink(link* headNode)//打印节点数据
{
 link* ptrmove = headNode->next;
 while (ptrmove)
 {
  printf("%d\n", ptrmove->data);
  ptrmove =ptrmove->next;
 }
 return 0;
}

void lastaddNode(link* headNode, int num)//在链表末尾添加一个新的节点
{
 link* Node1 = lastNode(headNode); 
 link* AddNode = creatNode(num);
 Node1->next = AddNode;
}

link* lastNode(link* headNode)//使当前指针指到链表最后一个节点。
{
 while (headNode->next)
 {
  headNode = headNode->next;
 }
 return headNode;
}

双链

双链只需要在数据结构上附加一个域,包含指向前一个单元的指针即可。

Floyd 判圈算法

判断是否有环
定义两个指针p1与p2,起始时,都指向链表的起点A,p1每次移动1个长度,p2每次移动2个长度。如果p2在移到链表的尾端时,并未与p1相遇,表明链表中不存在环。如果p1与p2相遇在环上的某一点C,表明链表有环。

环的长度
将指针p1固定在相遇位置C,移动p2,每次移动1个长度,并用变量计数。当p2再次与p1相遇时,此时该变量的值就是环的长度。

环的起点
将指针p1指向链表的起始位置A,指针p2仍在位置C,指针p1与p2每次均移动一个单位,p1与p2再次相遇的位置就是环的起点位置点B。

//判断链表中是否有环
struct Node
{
    int data;
    Node* next;
};
bool findCircle(Node* list) {
    if (list == NULL || list->next == NULL)
        return false;
    Node* ptr_slow = list, *ptr_fast = list;
    while (ptr_fast != ptr_slow) {
        if (ptr_fast == NULL || ptr_fast->next == NULL)
            return false;
        ptr_slow = ptr_slow->next;
        ptr_fast = ptr_fast->next->next;
    }
    return true;
}

//寻找环的起点
 Node *detectCycle(Node *head) {
       Node *ptr_slow = head, *ptr_fast = head;
        while (ptr_fast != NULL) {
            ptr_slow = ptr_slow->next;
            if (ptr_fast->ptr_next == NULL)
                return NULL;
            ptr_fast = ptr_fast->next->next;
            if (ptr_fast == ptr_slow) {
                Node *ptr = head;
                while (ptr != ptr_slow) {
                    ptr = ptr->next;
                    ptr_slow = ptr_slow->next;
                }
                return ptr;
            }
        }
        return NULL;
}

二叉查找树

struct DoubleTree
{
 int data;
 DoubleTree* lchild;
 DoubleTree* rchild;
};
DoubleTree* creatHead(int num) {
 DoubleTree* Tree = new DoubleTree;
 Tree->data = num;
 Tree->lchild = NULL;
 Tree->rchild = NULL;
 return Tree;
}
DoubleTree* MakeEmpty(DoubleTree* Tree) {
 if (Tree != NULL) {
  MakeEmpty(Tree->lchild);
  MakeEmpty(Tree->rchild);
  free(Tree);
 }
 return NULL;
}

DoubleTree* Find(DoubleTree* Tree, int Key) {
 if (Tree == NULL) return NULL;
 if (Key < Tree->data)
  return Find(Tree->lchild, Key);
 else if (Key > Tree->data)
  return Find(Tree->rchild, Key);
 else
  return Tree;
}

DoubleTree* FindMin(DoubleTree* Tree) {
 if (Tree == NULL) return NULL;
 else if (Tree->lchild == NULL) return Tree;
 else return FindMin(Tree->lchild);
}

DoubleTree* FindMax(DoubleTree* Tree) {
 if (Tree == NULL) return NULL;
 else if (Tree->rchild == NULL) return Tree;
 else return FindMax(Tree->rchild);
}

DoubleTree* insert(DoubleTree* Tree, int x) {
 if (Tree == NULL) {
  Tree = new DoubleTree;
  if (Tree == NULL) exit(1);
  else {
   Tree->data = x;
   Tree->lchild = NULL;
   Tree->rchild = NULL;
  }
 }
 else if (x < Tree->data)
  Tree->lchild = insert(Tree->lchild, x);
 else if (x > Tree->data)
  Tree->rchild = insert(Tree->rchild, x);
 return Tree;
}

DoubleTree* DeleteNode(DoubleTree* Tree, int x) {
 DoubleTree* temp;
 if (Tree == NULL) exit(1);
 else if ((x < Tree->data))
  Tree->lchild = DeleteNode(Tree->lchild, x);
 else if ((x > Tree->data))
  Tree->rchild = DeleteNode(Tree->rchild, x);
 else if (Tree->lchild && Tree->rchild) {
  temp = FindMin(Tree->rchild);
  Tree->data = temp->data;
  Tree->rchild = DeleteNode(Tree->rchild, Tree->data);
 }
 else {
  temp = Tree; 
  if (Tree->lchild == NULL) Tree = Tree->rchild;
  else if (Tree->rchild == NULL) Tree = Tree->lchild;
  free(temp);
 }
 return Tree;
}

void print(DoubleTree* Tree) {
 if (Tree == NULL)
  return;
 cout << Tree->data << " ";
 print(Tree->lchild);
 print(Tree->rchild);

}

AVL树(平衡二叉树)

AVLtree* SingerotateWihtleft(AVLtree* T) {//左左
 AVLtree* T1;
 T1 = T->lchild;
 T->lchild = T1->rchild;
 T1->rchild = T;
 T->height = max(returnHeight(T->lchild), returnHeight(T->rchild)) + 1;
 T1->height = max(returnHeight(T1->lchild), T->height) + 1;
 return T1;
}

AVLtree* SingerotateWihtright(AVLtree* T) {//右右
 AVLtree* T1;
 T1 = T->rchild;
 T->rchild = T1->lchild;
 T1->lchild = T;
 T->height = max(returnHeight(T->lchild), returnHeight(T->rchild)) + 1;
 T1->height = max(T->height, returnHeight(T1->rchild)) + 1;
 return T1;
}

AVLtree* DoublerotateWihtleft(AVLtree* T) {//左右
 T->lchild = SingerotateWihtright(T->lchild);
 return SingerotateWihtleft(T);
}

AVLtree* DoublerotateWihtright(AVLtree* T) {//右坐
 T->rchild = SingerotateWihtleft(T->rchild);
 return SingerotateWihtright(T);
}

AVLtree* insertNode(AVLtree* T,int x) {
 if (T == NULL) {
  T = new AVLtree;
  if (T == NULL) exit(1);
  else {
   T->data = x; T->height = 0;
   T->lchild = NULL; T->rchild = NULL;
  }
 }
 else if (x < T->data) {
  T->lchild = insertNode(T->lchild, x);
  if (returnHeight(T->lchild) - returnHeight(T->rchild) == 2) {
   if (x < T->lchild->data)
    T = SingerotateWihtleft(T);
   else
    T = DoublerotateWihtleft(T);
  }
 }
 else if (x > T->data) {
  T->rchild = insertNode(T->rchild, x);
  if (returnHeight(T->rchild) - returnHeight(T->lchild) == 2) {
   if (x > T->rchild->data)
    T = SingerotateWihtright(T);
   else
    T = DoublerotateWihtright(T);
  }
 }
 T->height = max(returnHeight(T->lchild), returnHeight(T->rchild))+1;
 return T;
}

AVLtree* DeleteNode(AVLtree* T, int num) {//太麻烦了,删除操作相对较少懒惰删除是最好的策略
 AVLtree* temp = NULL;
 if (T == NULL) cout << "NO data" << endl;
 else if (num < T->data)
  T->lchild = DeleteNode(T->lchild, num);
 else if (num > T->data)
  T->rchild = DeleteNode(T->rchild, num);
 else if (T->lchild && T->rchild) {
  temp = FindMin(T);
  T->data = temp->data;
  T->rchild = DeleteNode(T->rchild, T->data);
 }
 else {
  temp = T;
  if (T->lchild == NULL)
   T = T->rchild;
  else if (T->rchild == NULL)
   T = T->lchild;
  free(temp);
 }
 if (T!=NULL) {
  int lheight = returnHeight(T->lchild), rheight = returnHeight(T->rchild);
  T->height = (lheight > rheight ? lheight : rheight)+1;
  if (lheight - rheight == 2) {
   if (returnHeight(T->lchild->lchild) >= returnHeight(T->lchild->rchild))
    T = SingerotateWihtleft(T);
   else
    T = DoublerotateWihtleft(T);
  }
  else if (rheight - lheight == 2) {
   if (returnHeight(T->rchild->rchild) >= returnHeight(T->rchild->lchild))
    T = SingerotateWihtright(T);
   else
    T = DoublerotateWihtright(T);
  }
 }
 return T;
}

树状数组

树状数组的基本实现

时间复杂度为O(logN)
lowbit(x) = x&(-x); (说简单点就是x的二进制数从左往右数第一个1的位数,比如100的lowbit运算结果为3)
功能(在O(logN)的时间复杂度下)
单点加
查询前缀和
P3374 【模板】树状数组 1 - 洛谷 |

const int MAX = 5e5 + 5;
int a[MAX], c[MAX],n;
int query(int x) { //查询1-x的和
 int s = 0;
 for (; x; x -= x & (-x)) 
  s += c[x];
 return s;
}
void modify(int x, long long k) {//修改树状数组
 for (; x <= n; x += x & (-x) )
  c[x]+=k;
}
int main() {
 int  m; cin >> n >> m;
 for (int i = 1; i <= n; i++) {
  int num; cin >> num;
  modify(i, num);
 } 
 while (m--) {
  int choice,x,y; cin >> choice;
  if (choice == 1) {
   cin >> x >> y;
   modify(x, y);
  }
  else {
   cin >> x >> y;
   cout << query(y) - query(x-1) << endl; //计算x---y的和
  }
 }
 return 0;
}

区间加

对于数组a,区间[l,r]加上k,则对于其差分数组d(对于ai,ai=d1到di之和),dl+=k,dr-=k;
这里就不多赘述了,与上述模板相比,就是将树状数组c的初始值初始化为a的差分数组,然后再修改值的时候,加上一个modify(r+1,-k);
P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 5;
int  c[MAX], n;
int query(int x) { //查询1-x的和
    int s = 0;
    for (; x; x -= x & (-x))
        s += c[x];
    return s;
}
void modify(int x, long long k) {//修改树状数组
    for (; x <= n; x += x & (-x))
        c[x] += k;
}
int main() {
    int  m; cin >> n >> m;
    int last = 0;
    for (int i = 1; i <= n; i++) {
        int now; cin >> now;
        modify(i, now-last);
        last = now;
    }
    while (m--) {
        int choice, x, y, k ; cin >> choice;
        if (choice == 1) {
            cin >> x >> y>> k ;
            modify(x, k);
            modify(y + 1, -k);
        }
        else {
            cin >> x;
            cout << query(x) << endl;
        }
    }
    return 0;
}

一维树状数组基本应用

P1908 逆序对
对pair数组的first进行排序,这里的first表示值,second表示下标。后面在创建一个数组A,从pair数组的第一个值开始,使得A[pair[i].second]=i,最后可以使得数组A和原数组,数据大小是相反对应的,比如在下标3处,原数组存储的是最大值,A数组存储的则是最小值1。
最后将A数组,从第一个数开始,ans+=query(A[i]),再modify(A[i],1),这一操作,表示ans+=(以i为分界点,满足j< i,且Aj< Ai的元素个数),直到最后一个元素,最后ans就是最后的结果(因为答案要求的是j< i,且Aj>Ai,是相反的,正好A数组和原数组数据大小是相反对应的,所以最后的结果是正确的)

#include <bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> Pll;
int n, a[MAX], c[MAX];
Pll temp[MAX];
class cmp {
public:
    bool operator()(const Pll& p1, const Pll& p2) {
        if (p1.first > p2.first)
            return true;
        else if (p1.first == p2.first && p1.second > p2.second)
            return true;
        else
            return false;
    }
};

ll query(int x) {
    ll s = 0;
    for (; x; x -= x & (-x))
        s += c[x];
    return s;
}

void modify(int x, int d) {
    for (; x <= n; x += x & (-x))
        c[x] += d;
}
int main() {
    cin >> n;
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        cin >> temp[i].first;
        temp[i].second = i;
    }
    sort(temp + 1, temp + 1 + n, cmp());
    for (int i = 1; i <= n; i++)
        a[temp[i].second] = i;
    for (int i = 1; i <= n; i++) {
        ans += query(a[i]);
        modify(a[i], 1);
    }
    cout << ans << endl;
    return 0;
}

高维树状数组

这里以二维树状数组为例:
在一维树状数组中,tree[x](树状数组中的那个“数组”)记录的是右端点为x、长度为lowbit(x)的区间的区间和。
那么在二维树状数组中,可以类似地定义tree[x][y]记录的是右下角为(x, y),高为lowbit(x), 宽为 lowbit(y)的区间的区间和。
时间复杂度为 O(log2N)
基本实现和一位数状数组相差不多,拓展到k维树状数组就是嵌套k维数组,k 层for循环。

typedef long long ll;
const int MAX = 5 - 1;
ll c[MAX][MAX], n;
ll query(int x) {
    ll s = 0;
    for (int p=x; p; p -= p & (-p))
        for(int q=x;q;q-=q&(-q))    
            s += c[p][q];
    return s;
}

void modify(int x, ll d) {
    for (int p = x; p <= n; p += p & (-p))
        for (int q = x; q <= n; q += q & (-q))
            c[p][q] += d;
}

线段树

区间查询最小值

单点修改
区间查询(树状数组仅可以进行前缀和查询)
时间复杂度O(logN),可采用数组实现。
线段树可以进行多种查询,以下的板子为查询区间内的最小值。

#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5 + 5;

int a[MAX];

struct Node {
 int minv;
}tree[4*MAX];

//更新数据
void update(int id) {
 tree[id].minv = min(tree[id * 2].minv, tree[id * 2 + 1].minv);
}

//建树
void build(int id, int l, int r) {
 if (l == r) {
  tree[id].minv = a[l];
  return;
 }
 else {
  int mid = (l + r) / 2;
  build(id * 2, l, mid);
  build(id * 2+1, mid+1, r);
  update(id);
 }
}

//节点为id,将区间[l,r]的数据,a[pos]修改为val
void change(int id, int l, int r, int pos, int val) {
 if (l == r) {
  tree[id].minv = val;
  return;
 }
 else {
  int mid = (l + r) / 2;
  if (pos <= mid)
   change(id * 2, l, mid, pos, val);
  else 
   change(id * 2 + 1, mid + 1, r, pos, val);
  update(id);
 }
}

//[dl,dr]表示要查询的区间
int query(int id, int l, int r, int dl, int dr) {
 if (l == dl && r == dr)
  return tree[id].minv;
 int mid = (l + r) / 2;
 //[l,mid],[mid+1,r];
 if (dr <= mid)
  return query(2 * id, l, mid, dl, dr);
 else if (dl > mid)
  return query(2 * id + 1, mid + 1, r, dl, dr);
 else {
  // dr> mid, dl<=mid
  //[ql,mid],[mid+1,dr];
  return min(query(id * 2, l, mid, dl, mid), query(id * 2 + 1, mid + 1, r, mid+1, dr));
 }
}
int main() {
 // 3 2 4 6 8 9 5 3
 int n; cin >> n;
 for (int i = 1; i <= n; i++)
  cin >> a[i];
 build(1, 1, n);
 change(1, 1, n, 2, 99);
 cout << query(1, 1, n, 2, n - 1) << endl;
 return 0;
}

区间查询最大字段和

高度封装板子,wls真是永远的神

#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5 + 5;

int a[MAX];

struct info {
 int mPrefix,mSuffix,mSum,s;
 info(){}
 info(int a):mPrefix(a),mSuffix(a),mSum(a),s(a){}
};

info operator+(const info& left, const info& right) {
 info a;
 a.mSum = max({ left.mSum, right.mSum, left.mSuffix + right.mPrefix });
 a.mPrefix = max(left.mPrefix, left.s + right.mPrefix);
 a.mSuffix = max(right.mSuffix, right.s + left.mSuffix);
 a.s = left.s + right.s;
 return a;
}

struct Node {
 info val;
}tree[4 * MAX];

//更新数据
void update(int id) {
 tree[id].val = tree[id * 2].val + tree[id * 2 + 1].val;
}

//建树
void build(int id, int l, int r) {
 if (l == r) {
  tree[id].val = info(a[l]);
  return;
 }
 else {
  int mid = (l + r) / 2;
  build(id * 2, l, mid);
  build(id * 2+1, mid+1, r);
  update(id);
 }
}

//节点为id,将区间[l,r]的数据,a[pos]修改为val
void change(int id, int l, int r, int pos, int val) {
 if (l == r) {
  tree[id].val = info(val);
  return;
 }
 else {
  int mid = (l + r) / 2;
  if (pos <= mid)
   change(id * 2, l, mid, pos, val);
  else 
   change(id * 2 + 1, mid + 1, r, pos, val);
  update(id);
 }
}

//[dl,dr]表示要查询的区间
info query(int id, int l, int r, int dl, int dr) {
 if (l == dl && r == dr)
  return tree[id].val;
 int mid = (l + r) / 2;
 //[l,mid],[mid+1,r];
 if (dr <= mid)
  return query(2 * id, l, mid, dl, dr);
 else if (dl > mid)
  return query(2 * id + 1, mid + 1, r, dl, dr);
 else {
  // dr> mid, dl<=mid
  //[ql,mid],[mid+1,dr];
  return query(id * 2, l, mid, dl, mid)+query(id * 2 + 1, mid + 1, r, mid+1, dr);
 }
}

int main() {
 /*
  5 5
 -1 2 -3 4 -5
 2 4 5
 1 2 4
 2 1 5
 1 4 -1
 2 2 4
 */
 std::ios::sync_with_stdio(false); 
 cin.tie(0); cout.tie(0);
 int n,q; cin >> n>>q;
 for (int i = 1; i <= n; i++)
  cin >> a[i];
 build(1, 1, n);
 for (int i = 0; i < q; i++) {
  int ty; cin >> ty;
  if (ty == 1) {
   int x, d; cin >> x >> d;
   change(1, 1, n, x, d);
  }
  else {
   int l, r; cin >> l >> r;
   auto ans = query(1, 1, n, l, r);
   cout << ans.mSum << endl;
  }
 }
 return 0;
}

线段树打标记

用于线段树区间修改
打标记:
懒标记:懒标记的作用是记录每次、每个节点要更新的值
标记永久化:将标记永久记录( 不建议)
标记下传:因为线段树分成了一个一个区间,所以可以将某个节点的懒标记,下传给他的子结点,并且将当前节点的懒标记置为0。
标记合并:将之前存在的相同节点的多个标记合并
更新信息

查询区间最大值

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 5;

ll a[MAX];

struct info {
    ll maxv;
    info() {}
    info(ll x):maxv(x) {}
};


struct tag {
    ll add;
    tag(){}
    tag(ll x) :add(x) {}
};

info operator+(const info& left, const info& right) {
    return info(max(left.maxv, right.maxv));
}

info operator+(const info& val, const tag& t) {
    return info(val.maxv+t.add);
}

tag operator+(const tag& t1, const tag& t2) {
    return tag(t1.add+t2.add);
}

struct Node {
    tag t;
    info val;
}tree[4 * MAX];

//更新数据
void update(int id) {
    tree[id].val = tree[id * 2].val + tree[id * 2 + 1].val;
}

//设置懒标记
void settag(int id,tag t) {
    tree[id].val = tree[id].val + t;
    tree[id].t = tree[id].t + t;
}

//下传操作
void pushDown(int id) {
    if (tree[id].t.add != 0) {
        settag(id * 2, tree[id].t);
        settag(id * 2+1, tree[id].t);
        tree[id].t = 0;
    }
}

//建树
void build(int id, int l, int r) {
    if (l == r) {
        tree[id].val = info(a[l]);
        return;
    }
    else {
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        update(id);
    }
}

//节点为id,将区间[l,r]的数据,a[pos]修改为val
void modify(int id, int l, int r, int dl,int dr,tag t) {
    if (l == dl && r == dr) {
        settag(id, t);
        return;
    }
    int mid = (l + r) / 2;
    //[l,mid],[mid+1,r];
    pushDown(id);
    if (dr <= mid)
      modify(2 * id, l, mid, dl, dr,t);
    else if (dl > mid)
      modify(2 * id + 1, mid + 1, r, dl, dr,t);
    else {
        // dr> mid, dl<=mid
        //[ql,mid],[mid+1,dr];
        modify(id * 2, l, mid, dl, mid,t);
        modify(id * 2 + 1, mid + 1, r, mid + 1, dr,t);
    }
    update(id);
}

//[dl,dr]表示要查询的区间
info query(int id, int l, int r, int dl, int dr) {
    if (l == dl && r == dr)
        return tree[id].val;
    int mid = (l + r) / 2;
    //[l,mid],[mid+1,r];
    pushDown(id);
    if (dr <= mid)
        return query(2 * id, l, mid, dl, dr);
    else if (dl > mid)
        return query(2 * id + 1, mid + 1, r, dl, dr);
    else {
        // dr> mid, dl<=mid
        //[ql,mid],[mid+1,dr];
        return query(id * 2, l, mid, dl, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, dr);
    }
}

int main() {
    /*
     5 5
    1 2 3 4 5 
    2 4 5
    1 1 5 1 
    2 1 4
    1 2 3 10
    2 2 4
    */
    std::ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n, q; cin >> n >> q;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    build(1, 1, n);
    for (int i = 0; i < q; i++) {
        int ty; cin >> ty;
        if (ty == 1) {
            int l,r, d; cin >> l>>r >> d;
           modify(1, 1, n, l, r,tag(d));
        }
        else {
            int l, r; cin >> l >> r;
            auto ans = query(1, 1, n, l, r);
            cout << ans.maxv << endl;
        }
    }
    return 0;
}

查询区间和

可以进行区间乘和加某个数的操作

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e5 + 5;

ll a[MAX],mod;

struct Node {
    ll val = 0, size = 0;
    ll mul = 1, add = 0;
}tree[4 * MAX];

//更新数据
void update(int id) {
    tree[id].val = (tree[id * 2].val + tree[id * 2 + 1].val)%mod;
}

//设置懒标记
void settag(int id,ll add,ll mul) {
    tree[id].mul = (tree[id].mul * mul)%mod;
    tree[id].add = (tree[id].add*mul+add)%mod;
    tree[id].val = (tree[id].val*mul+tree[id].size*add)%mod ;
}

//下传操作
void pushDown(int id) {
    if (tree[id].add != 0 || tree[id].mul != 1) {
        settag(id * 2, tree[id].add,tree[id].mul);
        settag(id * 2+1, tree[id].add,tree[id].mul);
        tree[id].add = 0;
        tree[id].mul = 1;
    }
}

//建树
void build(int id, int l, int r) {
    tree[id].size = r - l + 1;
    if (l == r) {
        tree[id].val = a[l];
        return;
    }
    else {
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        update(id);
    }
}

//节点为id,将区间[l,r]的数据,增加t
void modify(int id, int l, int r, int dl,int dr,ll add,ll mul) {
    if (dl == l && r == dr) {
        settag(id, add,mul);
        return;
    }
    pushDown(id);
    int mid = (l + r) / 2;
    //[l,mid],[mid+1,r];
    if (dr <= mid)
       modify(2 * id, l, mid, dl, dr,add,mul);
    else if (dl > mid)
       modify(2 * id + 1, mid + 1, r, dl, dr,add,mul);
    else {
        // dr> mid, dl<=mid
        //[ql,mid],[mid+1,dr];
        modify(id * 2, l, mid, dl, mid,add,mul);
        modify(id * 2 + 1, mid + 1, r, mid + 1, dr, add, mul);
    }
    update(id);
}

//[dl,dr]表示要查询的区间
ll query(int id, int l, int r, int dl, int dr) {
    if (dl == l && r == dr)
        return tree[id].val;
    pushDown(id);
    ll mid = (l + r) / 2;
    //[l,mid],[mid+1,r];
    if (dr <=mid)                               
        return query(2 * id, l, mid, dl, dr);
    else if (dl > mid)
        return query(2 * id + 1, mid + 1, r, dl, dr);
    else {
        // dr> mid, dl<=mid
        //[ql,mid],[mid+1,dr];
        return (query(id * 2, l, mid, dl, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, dr))%mod;
    }
}


int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n, q; cin >> n >> q>>mod;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    build(1, 1, n);
    for (int i = 0; i < q; i++) {
        int ty; cin >> ty;
        if (ty == 1) {
            int l,r, k; cin >> l >> r >> k;
            modify(1, 1, n, l, r, 0, k);
        }
        else if(ty==2)
        {
            int l, r, d; cin >> l >> r >> d;
            modify(1, 1, n, l, r, d, 1);
        }
        else {
            int l, r; cin >> l >> r;
            auto ans = query(1, 1, n, l, r);
            cout << ans << endl;
        }
    }
    return 0;
}

优先队列(堆)

初始化

struct heapStruct {
    int Capacity;//规模
    int Size;//数据数量
    int *data;
};

heapStruct* Init(int MaxData) {
    heapStruct* H;
    H = new heapStruct;
    if (H == NULL) exit(1);
    H->data = new int[(MaxData + 1)];
    if (H->data == NULL) exit(1);
    H->Capacity = MaxData;
    H->Size = 0;
    H->data[0] = INT_MIN;
    return H;
}

插入

void insert(int x, heapStruct* H) {//
    int i;
    for (i = ++H->Size; H->data[i / 2] > x; i /= 2)//上滤操作,直到找到正确位置
        H->data[i] = H->data[i / 2];
    H->data[i] = x;
}

删除最小元

int DeleteMin(heapStruct* H) {
    int i, child;
    int MinData, LastData;
    MinData = H->data[1];
    LastData = H->data[H->Size--];
    for (int i = 1; i * 2 <= H->Size; i = child) {//下虑操作
        child = i * 2;
        if (child != H->Size && H->data[child + 1] < H->data[child])  child++;
        if (LastData > H->data[child]) H->data[i] = H->data[child];
        else
            break;
    }
    H->data[i] = LastData;
    return MinData;
}

哈曼夫树

哈夫曼树指的就是,WPL最小的二叉树,最优二叉树。通过构建哈夫曼树,我们可以很容易的求出最小WPL。我们可以采用优先队列(堆)的方式,求出最小WPL。

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
 priority_queue<int,vector<int>,greater<int>> h;
 int n,res=0; cin >> n;
 for (int i = 0; i < n; i++)
 {
  int num;
  cin >> num;
  h.push(num);
 }
 while (h.size()>1)
 {
  int sum = 0;
  sum += h.top(); h.pop();
  sum += h.top(); h.pop();
  h.push(sum);
  res += sum;
 }
 cout << res;
 return 0;
}

并查集

处理不相交可合并的集合关系的数据结构叫做并查集。使用前得初始化。

查询

int find(int n)
{
  return n==f[n]?n:(f[n]=find(f[n]));
}

合并

void merge(int i,int j)
{
   f[find(i)]=find(j);
}

典例题

P1551 亲戚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e6+5;
int f[MAX];
void init(int *f,int n)//初始化并查集
{
 for (int i = 1; i <= n; i++)
  f[i] = i;
}
 
int Find(int n)//查询父节点
{
 return n == f[n] ? n : (f[n] = Find(f[n]));
}
 
void merge(int i,int j)//合并
{
 f[Find(i)] = Find(j);
}
 
int main()
{
 int p1, p2;
 int n, m, p; cin >> n >> m >> p;
 init(f, n);
 for (int i = 1; i <= m; i++)
 {
  cin >> p1 >> p2;
  merge(p1, p2);
 }
 for (int j = 1; j <= p; j++)
 {
  cin >> p1 >> p2;
  if (Find(p1) == Find(p2))
   cout << "Yes" << endl;
  else
   cout << "No" << endl;
 }
 return 0;
}

哈希表/散列表

一种字符串hash:

typedef unsigned long long LL;
int prime = 233317;
LL mod = 212370440130137957ll,base=123;
LL Hash(char s[])
{
    int len = strlen(s);
    LL res = 0;
    for (int i = 0; i < len; i++)
        res = (res * base + (LL)s[i]) % mod + prime;
    return res;    
}

STL hash

C++ STL 自带hash。

#include <iostream>
using namespace std;

int main()
{
    string str = "Cirno is the strongest";
    hash<string> hash_fn;
    size_t str_hash = hash_fn(str);
    cout << str_hash;
    return 0;
}

解决冲突的方法

分离链接法

邻接矩阵

邻接表

#include <iostream>
#include <vector>
using namespace std;
const int MAX = 1000;
int m, n;//图中m顶点,n条边。
vector<pair<int, int>> P[MAX];
int v[MAX][MAX];
int main()
{
 cin >> m >> n;
 for (int i = 1; i <= m; i++)
 {
  int u, v, l; cin >> u >> v >> l;
  P[u].push_back({ v,l });
  //P[v].push_back({u,l})
  //如果是无向图,则还需要将终点,起始点颠倒过来存储一下
 }
 
 //将邻接表转换为邻接矩阵
 for (int i = 1; i <= m; i++)
  for (int j = 0; j < P[i].size(); j++)
   v[i][P[i][j].first] = P[i][j].second;
 //对于邻接矩阵v[i][j],j就是终点,即为P[i][j].first,
 //其储存的值为边权,也就是P[i][j].second
 
 
 //output
 for (int i = 1; i <= m; i++) {
  for (int j = 1; j <= m; j++)
   cout << v[i][j] << " ";
  cout << endl;
 }
 return 0;
}

图的遍历

DFS遍历

void dfs(int x) 
{
 vis[x] = true;
 cout << x << " "; 
 for (int i=0; i<P[x].size(); i++)
  if (!vis[P[x][i]]) 
            dfs(P[x][i]);
}

BFS遍历

queue<int> q;
void bfs(int x) 
{
 memset(vis, false, sizeof(vis));
 vis[x] = true;
 q.push(x);
 while (!q.empty())
     {
  int v = q.front();
  q.pop();
  cout << v << " ";
  for (int i=0; i<P[v].size(); i++) 
   if (!vis[P[v][i]]) 
            {
    vis[P[v][i]] = true;
    q.push(P[v][i]);
   }
 }
}

DAG与拓扑排序

DAG:对于一个图而言,如果这个图是没有环的,但是边是有方向的,那么就称之为有向无环图,即DAG。

拓扑排序:拓扑排序就是在DAG的基础上对点进行排序,使得在搜到点x时所有能到达点x的点y已经被搜过了。
其具体实现流程如下:
1.将所有入度为0的点加入处理队列
2.将处于队头的点x取出,遍历x所能到达的所有点y。

  1. 对于每一个y,删除从点x到点y的边。
    4.如果点y的入度减为0了,说明说明所有能到y的点都被计算过了,再将点y加入处理队列。
    5.重复2,直到队列为空。
    一道用到拓扑排序的经典例题:
    P4017 最大食物链计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 10;
const int MOD = 80112002;
vector<int> V[MAX];
queue<int> Q;
int ind[MAX], outd[MAX], f[MAX];//入度,出度,食物链数
int main() 
{
    int n, m, res = 0; cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        int x, y; cin >> x >> y;
        outd[x]++; ind[y]++;
        V[x].push_back(y);
    }
 
    for (int i = 1; i <= n; i++) {
        if (ind[i] == 0)
            Q.push(i),f[i]=1;
    }//将入度为0的数据入队
 
    while (!Q.empty()) {
        int x = Q.front(); Q.pop();
        for (int i = 0; i < V[x].size(); i++) {
            int y = V[x][i];
            f[y] = (f[x] + f[y]) % MOD;
            ind[y]--;
            if (ind[y] == 0)
                Q.push(y);//入度为0,就入队
        }
    }
 
    for (int i = 1; i <= n; i++)
    {
        if (outd[i] == 0)
            res = (res + f[i]) % MOD;
    }
    cout << res;
    return 0;
}

最短路问题

无权最短路问题

运用到了BFS ,没有访问到的标记为INF。

#include <bits/stdc++.h>
using namespace std;
const int MAX = 1e6;
const int MOD = 100003;
struct Node {
    bool vis=false;
    int Dist = INFINITY;
    int path;
};
vector<int> V[MAX];
queue<int> Q;
Node NodeArr[MAX];
int main() {
    int m, n; cin >> m >> n;
    for (int i = 1; i <= n; i++) {
        int x, y; cin >> x >> y;
        V[x].push_back(y);
        V[y].push_back(x);
    }
    for (int i = 1; i <= m; i++) {
        NodeArr[i].vis = false;
        NodeArr[i].Dist = INFINITY;
    }
    Q.push(1); NodeArr[1].Dist = 0; NodeArr[1].vis = true;
    while (!Q.empty()) {
        int x = Q.front(); Q.pop();
        for (int i = 0; i < V[x].size(); i++) {
            if (NodeArr[V[x][i]].vis==false) {
                NodeArr[V[x][i]].vis = true;
                NodeArr[V[x][i]].Dist = NodeArr[x].Dist + 1;
                NodeArr[V[x][i]].path = x;
                Q.push(V[x][i]);
            }
        }
    }
    for (int i = 1; i <= m; i++)
        cout << NodeArr[i].Dist << endl;
    return 0;
}

P1144 最短路计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
以下为这题题解,这题除了可以求出1到各个结点的无权最短路长度以外,还可以求出到不同结点一共有多少种最短路情况。

#include <bits/stdc++.h>
using namespace std;
const int MAX = 1e6;
const int MOD = 100003;
vector<int> V[MAX];
queue<int> Q;
bool judge[MAX];
int cnt[MAX],depth[MAX];
int main() {
 int m, n; cin >> m >> n;
 for (int i = 1; i <= n; i++) {
  int x, y; cin >> x >> y;
  V[x].push_back(y);
  V[y].push_back(x);
 }
 for (int i = 1; i <= m; i++) sort(V[i].begin(), V[i].end());
 Q.push(1); judge[1] = 1; cnt[1] = 1;
 while (!Q.empty()) {
  int x = Q.front(); Q.pop();
  for (int i = 0; i < V[x].size(); i++) {
   int n = V[x][i];
   if (!judge[n]) {
    judge[n] = 1;
    depth[n] = depth[x] + 1;
    Q.push(n);
   }
   if(depth[n]==depth[x]+1) cnt[n]=(cnt[n]+cnt[x])%MOD;
  }
 }
 for (int i = 1; i <= m; i++)
  cout << cnt[i] << endl;
 return 0;
}

Dijkstra算法

基于贪心思想,使用堆优化,可以求得有权最短路径的结果

#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5+5;
const int INF = 2147483647;

struct Node {
    int dist = INF;
    bool vis = false;
};

typedef pair<int, int> pll;

Node NodeArr[MAX];
vector<pll> V[MAX];
priority_queue<pll,vector<pll>,greater<pll>> Q;

void Dijkstra(vector<pair<int, int>> V[], int start) {
    NodeArr[start].dist = 0;
    Q.push({ 0,start }); 
    while (!Q.empty()) {
        int x = Q.top().second; Q.pop();
        if (NodeArr[x].vis)
            continue;
        NodeArr[x].vis = true;
        for (int i = 0; i < V[x].size(); i++)
        {
            int temp = NodeArr[x].dist + V[x][i].first;
            if (NodeArr[V[x][i].second].dist > temp)
                NodeArr[V[x][i].second].dist = temp,Q.push({ NodeArr[V[x][i].second].dist, V[x][i].second });
        }
    }.
}
int main() {
    int n, m, s; cin >> n >> m >> s;
    for (int i = 1; i <= m; i++) {
        int x, y, l; scanf("%d%d%d", &x, &y, &l);
            V[x].push_back({ l, y });
    }
    Dijkstra(V, s);
    for (int i = 1; i <= n; i++)
        printf("%d ", NodeArr[i].dist);
    return 0;
}

floyd算法

可以求出任意两节点之间的最短路径,因此它是比Dijkstra更一般的算法。

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

const int MAX = 5e3 + 5;
const int INF = 2e5;

int Martix[MAX][MAX];
int n, m;

int pathMatirx[MAX][MAX];//存储中间点
int shortPath[MAX][MAX];//存储两边之间最小值

void floyd(int x,int y) {//起点,终点

 //init
 for (int i = 1; i <= n; i++)
  for (int j = 1; j <= n; j++) {
   pathMatirx[i][j] = j;
   shortPath[i][j] = Martix[i][j];
  }

 for (int i = 1; i <= n; i++)
  for (int j = 1; j <= n; j++)
   for (int k = 1; k <= n; k++) {
    if (shortPath[j][k] > (shortPath[j][i] + shortPath[i][k])) {
     shortPath[j][k] = shortPath[j][i] + shortPath[i][k];
     pathMatirx[j][k] = pathMatirx[j][i];
    }
   }

 for (int i = 1; i <= n; i++) {
  for (int j = 1; j <= n; j++)
   cout << shortPath[i][j] << " ";
  cout << endl;
 }

 cout << x << " ";
 int k = pathMatirx[x][y];
 while (k != y) {
  cout << k << " ";
  k = pathMatirx[k][y];
 }
 cout << y << endl;
 cout << shortPath[x][y] << endl;
}

int main() {
 std::ios::sync_with_stdio(false);
 cin.tie(0);
 cin >> n >> m;
 for (int i = 1; i <= n; i++)
  for (int j = 1; j <= n; j++) {
   if (i == j)
    Martix[i][j] = 0;
   else
    Martix[i][j] = INF;
  }
 for (int i = 1; i <= m; i++) {
  int x, y, v; cin >> x >> y >> v;
  Martix[x][y] = v;
  Martix[y][x] = v;
 }
 floyd(1,2);
 return 0;
}

最小生成树

Prim算法

算法和dijkstra非常像,甚至可以说是一模一样.

#include <bits/stdc++.h>
using namespace std;
const int MAX = 2e5 + 5;
const int INF = 1<<30;
typedef pair<long , long > pll;//权重,终点
struct Node {
 long long dist = INF;
 bool vis = false;
} node[MAX];

priority_queue<pll, vector<pll>, greater<pll>> q;
vector<pll> V[MAX];
int N, M;
void prim() {
 int ans = 0, tot = 0;
 q.push({ 0,1 });
 node[1].dist = 0;
 while (!q.empty()) {
  pll x = q.top();
  q.pop();
  if (node[x.second].vis)
   continue;
  tot++;
  ans += node[x.second].dist;
  node[x.second].vis = true;
  for (auto i : V[x.second]) {
   if (!node[i.second].vis && i.first < node[i.second].dist) {
    node[i.second].dist = i.first;
    q.push({ node[i.second].dist,i.second });
   }
  }
 }
 if (tot != N)
  cout << "orz" << endl;
 else
  cout << ans << endl;
}

int main(void) {
 std::ios::sync_with_stdio(false);
 cin.tie(0); cout.tie(0);
 cin >> N >> M;
 for (int i = 1; i <= M; i++) {
  int x, y, l;
  cin >> x >> y >> l;
  V[x].push_back({ l,y });
  V[y].push_back({ l,x });
 }
 prim();
 return 0;
}

Kruskal算法

也是一种求最小生成树的算法,使用到了并查集的知识,将结点根据权值有小到大进行排列,从第一个结点开始遍历,如果结点起点和终点不属于同一个集合,则将两点合并到同一个集合,并且使计数cnt--,ans增加起点到终点的权值,最后可以到达求最小生成树的目的。其基本思想是贪心。

#include <bits/stdc++.h>
using namespace std;
const int MAX=2e5+5;

int m,n,f[MAX];

struct Node{
 int x,y,v;
 bool operator<(const Node& a) const{
  return v<a.v;
 }
} M[MAX];

int find(int n){
 return n==f[n]?n:(f[n]=find(f[n]));
}

void merge(int x,int y){
 f[find(x)]=find(y);
}

void init(int n){
 for(int i=1;i<=n;i++)
  f[i]=i;
}

void Kurskal(){
 init(m);
 sort(M+1,M+1+n);
 int ans=0,cnt=m;
 for(int i=1;i<=n;i++){
  int x=find(M[i].x),y=find(M[i].y); 
  if(x!=y){
   merge(x,y);
   ans+=M[i].v;
   cnt--;
  }
  if(cnt==1)
   break;
 }
 if(cnt!=1)
  cout<<"orz"<<endl;
 else 
  cout<<ans<<endl;
}

int main(){
 cin>>m>>n;
 for(int i=1;i<=n;i++){
  cin>>M[i].x>>M[i].y>>M[i].v;
 }
 Kurskal();
 return 0;
}

java快读类

鉴定为十分有用

    /** 快速输入类 */
    static class Reader {
        static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        static StringTokenizer tokenizer = new StringTokenizer("");
        /** 获取下一段文本 */
        static String next() throws IOException {
            while ( ! tokenizer.hasMoreTokens() ) {
                tokenizer = new StringTokenizer(reader.readLine());
            }
            return tokenizer.nextToken();
        }
        static int nextInt() throws IOException {
            return Integer.parseInt( next() );
        }
        static double nextDouble() throws IOException {
            return Double.parseDouble( next() );
        }
    }
posted on 2024-10-27 17:12  TheWiseCirno  阅读(3)  评论(0编辑  收藏  举报