acwing的算法学习笔记

算法

动态规划

完全背包


#include<iostream>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1 ; i <= n ;i ++)
    {
        cin>>v[i]>>w[i];
    }

    for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }
    cout<<f[m]<<endl;
}

多重背包image-20220502132412658

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

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

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

    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i] >> s[i];

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

    cout << f[n][m] << endl;
    return 0;
}

线性dp

最长上升子序列

#include <iostream>

using namespace std;

const int N = 1010;

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

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

    int mx = 1;    // 找出所计算的f[i]之中的最大值,边算边找
    for (int i = 0; i < n; i++) {
        f[i] = 1;    // 设f[i]默认为1,找不到前面数字小于自己的时候就为1
        for (int j = 0; j < i; j++) {
            if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1);    // 前一个小于自己的数结尾的最大上升子序列加上自己,即+1
        }
        mx = max(mx, f[i]);
    }

    cout << mx << endl;
    return 0;
}

最短编辑距离

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N];
int f[N][N];//将a[1~i]变成b[1~j]的操作方式

int main()
{
    scanf("%d%s", &n, a + 1);
    scanf("%d%s", &m, b + 1);

    for (int i = 0; i <= m; i ++ ) f[0][i] = i;
    //将a[0](即没有字符)转变为b[i]需要添加i次
    for (int i = 0; i <= n; i ++ ) f[i][0] = i;
    //将a[i]转变为b[0](没有字符),即需要删除i次

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            //先比较删除和添加的方案数,再比较替换的方案数
            if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
            else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
        }

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

    return 0;
}

区间dp

image-20220520173110381

#include <iostream>
#include <cstring>

using namespace std;

const int N = 307;

int a[N], s[N];
int f[N][N];

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

    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        s[i] += s[i - 1] + a[i];
    }

    memset(f, 0x3f, sizeof f);
    // 区间 DP 枚举套路:长度+左端点 
    for (int len = 1; len <= n; len ++) { // len表示[i, j]的元素个数
        for (int i = 1; i + len - 1 <= n; i ++) {
            int j = i + len - 1; // 自动得到右端点
            if (len == 1) {
                f[i][j] = 0;  // 边界初始化
                continue;
            }

            for (int k = i; k <= j - 1; k ++) { // 必须满足k + 1 <= j
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
            }
        }
    }

    cout << f[1][n] << endl;


    return 0;
}

作者:白马金羁侠少年
链接:https://www.acwing.com/solution/content/13945/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

状态压缩dp

image-20220520173235471

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


const int N = 12, M = 1<< N;  

long long f[N][M] ;// 第一维表示列, 第二维表示所有可能的状态

bool st[M];  //存储每种状态是否有奇数个连续的0,如果奇数个0是无效状态,如果是偶数个零置为true。

//vector<int > state[M];  //二维数组记录合法的状态
vector<vector<int>> state(M);  //两种写法等价:二维数组

int m, n;

int main() {

    while (cin >> n >> m, n || m) { //读入n和m,并且不是两个0即合法输入就继续读入

        //第一部分:预处理1
        //对于每种状态,先预处理每列不能有奇数个连续的0

        for(int i = 0; i < (1 << n); i ++) {

            int cnt = 0 ;//记录连续的0的个数

            bool isValid = true; // 某种状态没有奇数个连续的0则标记为true

            for(int j = 0; j < n; j ++) { //遍历这一列,从上到下

                 if ( (i >> j) & 1) {  
                     //i >> j位运算,表示i(i在此处是一种状态)的二进制数的第j位; 
                     // &1为判断该位是否为1,如果为1进入if
                    if (cnt & 1) { 
                    //这一位为1,看前面连续的0的个数,如果是奇数(cnt &1为真)则该状态不合法
                        isValid =false; break;
                    } 

                    cnt = 0; // 既然该位是1,并且前面不是奇数个0(经过上面的if判断),计数器清零。
                    //其实清不清零没有影响
                 }
                 else cnt ++; //否则的话该位还是0,则统计连续0的计数器++。
            }
            if (cnt & 1)  isValid = false; //最下面的那一段判断一下连续的0的个数

            st[i]  = isValid; //状态i是否有奇数个连续的0的情况,输入到数组st中
        }

        //第二部分:预处理2
        // 经过上面每种状态 连续0的判断,已经筛掉一些状态。
        //下面来看进一步的判断:看第i-2列伸出来的和第i-1列伸出去的是否冲突

        for (int j = 0; j < (1 << n); j ++) { //对于第i列的所有状态
            state[j].clear(); //清空上次操作遗留的状态,防止影响本次状态。

            for (int k = 0; k < (1 << n); k ++) { //对于第i-1列所有状态
                if ((j & k ) == 0 && st[ j | k]) 
                // 第i-2列伸出来的 和第i-1列伸出来的不冲突(不在同一行) 
                //解释一下st[j | k] 
                //已经知道st[]数组表示的是这一列没有连续奇数个0的情况,
                //我们要考虑的是第i-1列(第i-1列是这里的主体)中从第i-2列横插过来的,
                //还要考虑自己这一列(i-1列)横插到第i列的
                //比如 第i-2列插过来的是k=10101,第i-1列插出去到第i列的是 j =01000,
                //那么合在第i-1列,到底有多少个1呢?
                //自然想到的就是这两个操作共同的结果:两个状态或。 j | k = 01000 | 10101 = 11101
                //这个 j|k 就是当前 第i-1列的到底有几个1,即哪几行是横着放格子的

                    state[j].push_back(k);  
                    //二维数组state[j]表示第j行, 
                    //j表示 第i列“真正”可行的状态,
                    //如果第i-1列的状态k和j不冲突则压入state数组中的第j行。
                    //“真正”可行是指:既没有前后两列伸进伸出的冲突;又没有连续奇数个0。
            }

        }

        //第三部分:dp开始

        memset(f, 0, sizeof f);  
        //全部初始化为0,因为是连续读入,这里是一个清空操作。
        //类似上面的state[j].clear()

        f[0][0] = 1 ;// 这里需要回忆状态表示的定义
        //按定义这里是:前第-1列都摆好,且从-1列到第0列伸出来的状态为0的方案数。
        //首先,这里没有-1列,最少也是0列。
        //其次,没有伸出来,即没有横着摆的。即这里第0列只有竖着摆这1种状态。

        for (int i = 1; i <= m; i ++) { //遍历每一列:第i列合法范围是(0~m-1列)
            for (int j = 0; j < (1<<n); j ++) {  //遍历当前列(第i列)所有状态j
                for (auto k : state[j])    // 遍历第i-1列的状态k,如果“真正”可行,就转移
                    f[i][j] += f[i-1][k];    // 当前列的方案数就等于之前的第i-1列所有状态k的累加。
            }
        }

        //最后答案是什么呢?
        //f[m][0]表示 前m-1列都处理完,并且第m-1列没有伸出来的所有方案数。
        //即整个棋盘处理完的方案数

        cout << f[m][0] << endl;

    }
}   

树形dp

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

#define N 6010

bool vis[N]; // 判断此小弟是否有上司
int head[N],cnt;
int f[N][2],happy[N],n;

struct edge
{
    int to,nex;
}e[N];

void add(int from,int to)
{
    e[++cnt] = (edge){to,head[from]};
    head[from] = cnt;
}

void dfs(int boss)
{
    f[boss][1] = happy[boss]; // 1代表这个boss要来,先加上他来的利益

    for (int i = head[boss]; i; i = e[i].nex)
    {
        int too = e[i].to;
        dfs(too); // 递归一直搜

        f[boss][0] += max(f[too][0] , f[too][1]); // boss不来,那小弟就是王了,但小弟要以利益为重,如果小小弟来可以获利更大,就让小小弟来
        f[boss][1] += f[too][0]; // boss来了!!!小弟都承让
    }
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> happy[i];
    for (int i = 1; i <= n - 1; i++)
    {
        int a,b;
        cin >> a >> b;
        add(b , a);
        vis[a] = true; // a有上司了(以后出行要小心)
    }

    int root = 1;
    while (vis[root]) root ++; // 找到根节点(没有上司的董事长)

    dfs(root);

    cout << max(f[root][0] , f[root][1]);// 取董事长来和不来的最大利益
    return 0;
}

数据结构

单链表数组模拟

#include <iostream>

using namespace std;

const int N = 100010;


// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
    head = -1;
    idx = 0;
}

// 将x插到头结点
void add_to_head(int x)
{
    e[idx] = x;
    ne[idx] = head;
     head = idx ++ ;
}

// 将x插到下标是k的点后面
void add(int k, int x)
{
    e[idx] = x, 
    ne[idx] = ne[k], 
    ne[k] = idx ++ ;
}

// 将下标是k的点后面的点删掉
void remove(int k)
{
    ne[k] = ne[ne[k]];
}

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

    init();

    while (m -- )
    {
        int k, x;
        char op;

        cin >> op;
        if (op == 'H')
        {
            cin >> x;
            add_to_head(x);
        }
        else if (op == 'D')
        {
            cin >> k;
            if (!k) head = ne[head];
            else remove(k - 1);
        }
        else
        {
            cin >> k >> x;
            add(k - 1, x);
        }
    }

    for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
    cout << endl;

    return 0;
}

数组模拟队列

#include<iostream>

using namespace std;
const int N = 1e5 + 10;

int q[N];

int main() {
    int n;
    cin >> n;
    int hh = 0, tt = 0;
    while (n--) {
        string op;
        int x;
        cin >> op;
        if (op == "push") {
            cin >> x;
            q[tt++] = x;
        } else if (op == "pop") {
            hh++;
        } else if (op == "empty") {
            if (hh < tt) puts("NO");
            else puts("YES");
        } else {
            cout << q[hh] << endl;
        }
    }
}

作者:松鼠爱葡萄
链接:https://www.acwing.com/solution/content/19039/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

数组模拟栈

#include <iostream>
using namespace std;
const int N = 100010;
int st[N];
int top = -1;
int n;
int main()
{
    cin >> n;
    while(n--)
    {
        string s;
        cin >> s;

        //栈顶所在索引往后移动一格,然后放入x。
        if(s == "push")
        {
            int a;
            cin >> a;
            st[++top] = a;
        }

        //往前移动一格
        if(s == "pop")
        {
            top --;
        }
        //返回栈顶元素
        if(s == "query")
        {
            cout << st[top] << endl;
        }
        //大于等于 0 栈非空,小于 0 栈空
        if(s == "empty")
        {
            cout << (top == -1 ? "YES" : "NO") << endl;
        }
    }
}

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

数组模拟邻接表

const int N = 1010, M = 1010;

int h[N], e[M], w[M], nxt[M], eidx;

void add(int u, int v, int weight)   // 添加有向边 u->v, 权重为weight
{
    e[eidx] = v;        // 记录边的终点
    w[eidx] = weight;   // 记录边的权重
    nxt[eidx] = h[u];   // 将下一条边指向结点u此时的第一条边
    h[u] = eidx;        // 将结点u的第一条边的编号改为此时的eidx
    eidx++;             // 递增边的编号edix, 为将来使用
}

void iterate(int u)   // 遍历结点u的所有邻边
{
    // 从u的第一条边开始遍历,直到eid==-1为止
    for(int eid = h[u]; eid != -1; eid = nxt[eid])
    {
        int v = e[eid];
        int weight = w[eid];
        cout << u << "->" << v << ", weight: " << weight << endl;
    }
}

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

    memset(h, -1, sizeof h);  // 初始化h数组为-1
    eidx = 0;                 // 初始化边的编号为0

    while(m--)
    {
        int u, v, weight;
        cin >> u >> v >> weight;
        add(u, v, weight);
    }

    for(int u = 1; u <= n; ++u)
        iterate(u);

    return 0;
}

二叉搜索树

#include<iostream>

using namespace std;

typedef int status;

//定义一个树的结构体
typedef struct BiTNode
{
    int data;
    struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;

//函数声明
void CreateBST(BiTree* T, int a[], int n);
void outputBST(BiTree T);
status InsertBST(BiTree* T, int key);
status DeleteBST(BiTree*T,int key);
status Delete(BiTree *p);

//二叉排序树的查找函数定义
status SearchBST(BiTree T,int key,BiTree f,BiTree *p)
{
    if (!T)
    {
        *p = f;
        return false;
    }
    else if (key==T->data)
    {
        *p = T;
        return true;
    }
    else if (key<T->data)
    {
        return SearchBST(T->lchild,key,T,p);
    }
    else
    {
        return SearchBST(T->rchild,key,T,p);
    }
}

//二叉排序树的插入函数定义
status InsertBST(BiTree *T,int key)
{
    BiTree p=NULL, s=NULL;
    if (!SearchBST(*T,key,NULL,&p))
    {
        s = (BiTree)malloc(sizeof(BiTNode));
        s->data = key;
        s->lchild = s->rchild = NULL;
        if (!p)
            * T = s;
        else if (key < p->data)
            p->lchild = s;
        else
            p->rchild = s;
        return true;
    }
    return false;
}

//二叉排序树的删除操作函数定义
status DeleteBST(BiTree* T, int key)
{
    if (!*T)
        return false;
    else
    {
        if (key == (*T)->data)
        {
            return Delete(T);
        }
        else if (key<(*T)->data)
        {
            return DeleteBST(&(*T)->lchild,key);
        }
        else
        {
            return DeleteBST(&(*T)->rchild,key);
        }
    }
}

//根据节点的三种情况来删除节点
status Delete(BiTree* p)
{
    BiTree q, s;
    if ((*p)->rchild==NULL)
    {
        q = *p; *p = (*p)->lchild; free(q);
    }
    else if ((*p)->lchild==NULL)
    {
        q = *p; *p = (*p)->rchild; free(q);
    }
    else
    {
        q = *p; s = (*p)->lchild;
        while (s->rchild)
        {
            q = s; s = s->rchild;
        }
        (*p)->data = s->data;
        if (q != *p)
            q->rchild = s->lchild;
        else
            q->lchild = s->lchild;
        free(s);
    }
    return true;
}

//通过一个数组来创建二叉排序树
void CreateBST(BiTree*T, int a[], int n)
{
    int i;
    for (i = 0; i < n; i++)
    {
        InsertBST(T, a[i]);
    }
}

//把一个二叉排序树中序遍历打印
void outputBST(BiTree T)
{
    if (T == NULL)
    {
        return;
    }
    outputBST(T->lchild);
    cout << T->data << " ";
    outputBST(T->rchild);
}

//主函数
int main()
{
    int a[] = { 62,88,58,47,35,73,51,99,37,93 };
    BiTree T = NULL;

    //创建二叉排序树
    CreateBST(&T, a, 10);

    //在二叉排序树中插入95
    InsertBST(&T, 95);

    //在二叉排序树中查找节点
    int b = 95;
    BiTree p = NULL;
    if (!SearchBST(T, b, NULL, &p))
        cout << "没有找到" << endl;
    else
    {
        cout << b << "查找结果的指针为:\n" << p << endl;
    }

    //在二叉排序树中删除88节点
    DeleteBST(&T, 88);

    //验证上述的插入和删除操作
    outputBST(T);
    cout << endl;

    return 0;

哈希表

拉链法

#include <cstring>
#include <iostream>

using namespace std;

const int N = 1e5 + 3;  // 取大于1e5的第一个质数,取质数冲突的概率最小 可以百度

//* 开一个槽 h
int h[N], e[N], ne[N], idx;  //邻接表

void insert(int x) {
    // c++中如果是负数 那他取模也是负的 所以 加N 再 %N 就一定是一个正数
    int k = (x % N + N) % N;
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx++;
}

bool find(int x) {
    //用上面同样的 Hash函数 讲x映射到 从 0-1e5 之间的数
    int k = (x % N + N) % N;
    for (int i = h[k]; i != -1; i = ne[i]) {
        if (e[i] == x) {
            return true;
        }
    }
    return false;
}

int n;

int main() {
    cin >> n;

    memset(h, -1, sizeof h);  //将槽先清空 空指针一般用 -1 来表示

    while (n--) {
        string op;
        int x;
        cin >> op >> x;
        if (op == "I") {
            insert(x);
        } else {
            if (find(x)) {
                puts("Yes");
            } else {
                puts("No");
            }
        }
    }
    return 0;
}

开放寻址法

#include <cstring>
#include <iostream>

using namespace std;

//开放寻址法一般开 数据范围的 2~3倍, 这样大概率就没有冲突了
const int N = 2e5 + 3;        //大于数据范围的第一个质数
const int null = 0x3f3f3f3f;  //规定空指针为 null 0x3f3f3f3f

int h[N];

int find(int x) {
    int t = (x % N + N) % N;
    while (h[t] != null && h[t] != x) {
        t++;
        if (t == N) {
            t = 0;
        }
    }
    return t;  //如果这个位置是空的, 则返回的是他应该存储的位置
}

int n;

int main() {
    cin >> n;

    memset(h, 0x3f, sizeof h);  //规定空指针为 0x3f3f3f3f

    while (n--) {
        string op;
        int x;
        cin >> op >> x;
        if (op == "I") {
            h[find(x)] = x;
        } else {
            if (h[find(x)] == null) {
                puts("No");
            } else {
                puts("Yes");
           }
        }
    }
    return 0;
}


并查集

#include<iostream>
using namespace std;
int f[100010]={0},n,m,sum=0;
void init(){
	int i;
	for(i=1;i<=n;i++){
		f[i]=i;
	}
	return ;
}
int getf(int v){
	if(f[v]==v){
		return v;
	}
	else{
		f[v]=getf(f[v]);
		return f[v];
	}
}
void merge(int v,int u){
	int t1,t2;
	t1=getf(f[v]);
	t2=getf(f[u]);
	if(t1!=t2){
		f[t2]=t1;
	}
	return;
}
int main(){
	cin>>n>>m;
	init();
	for(int i=0;i<m;i++){
		char op;
		cin>>op;
		int v,u;
		cin>>v>>u;
		if(op=='M')
		merge(v,u);
		else{
			if(getf(v)==getf(u))
				printf("Yes\n");
			else
			printf("No\n");
			}
	}
	return 0;
}

哈夫曼树

typedef struct HNode {
  int weight;
  HNode *lchild, *rchild;
} * Htree;

Htree createHuffmanTree(int arr[], int n) {
  Htree forest[N];
  Htree root = NULL;
  for (int i = 0; i < n; i++) {  // 将所有点存入森林
    Htree temp;
    temp = (Htree)malloc(sizeof(HNode));
    temp->weight = arr[i];
    temp->lchild = temp->rchild = NULL;
    forest[i] = temp;
  }

  for (int i = 1; i < n; i++) {  // n-1 次循环建哈夫曼树
    int minn = -1, minnSub;  // minn 为最小值树根下标,minnsub 为次小值树根下标
    for (int j = 0; j < n; j++) {
      if (forest[j] != NULL && minn == -1) {
        minn = j;
        continue;
      }
      if (forest[j] != NULL) {
        minnSub = j;
        break;
      }
    }

    for (int j = minnSub; j < n; j++) {  // 根据 minn 与 minnSub 赋值
      if (forest[j] != NULL) {
        if (forest[j]->weight < forest[minn]->weight) {
          minnSub = minn;
          minn = j;
        } else if (forest[j]->weight < forest[minnSub]->weight) {
          minnSub = j;
        }
      }
    }

    // 建新树
    root = (Htree)malloc(sizeof(HNode));
    root->weight = forest[minn]->weight + forest[minnSub]->weight;
    root->lchild = forest[minn];
    root->rchild = forest[minnSub];

    forest[minn] = root;     // 指向新树的指针赋给 minn 位置
    forest[minnSub] = NULL;  // minnSub 位置为空
  }
  return root;
}

线段树

#include<iostream>
using namespace std;

const int N = 2e5 + 5;
typedef long long LL;

//线段树的结点, 最大空间开4倍
struct Node{
    int l, r;
    int v;   //最大值
}tr[N * 4];

int m, p;

//u为当前线段树的结点编号
void build(int u, int l, int r) {
    tr[u] = {l, r};
    if(l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

//查询以u为根节点,区间[l, r]中的最大值
int query(int u, int l, int r) {
    //      Tl-----Tr
    //   L-------------R   
    //1.不必分治,直接返回
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;

    int mid = tr[u].l + tr[u].r >> 1;
    int v = 0;
    //     Tl----m----Tr
    //        L-------------R 
    //2.需要在tr的左区间[Tl, m]继续分治
    if(l <= mid) v = query(u << 1, l, r);

    //     Tl----m----Tr
    //   L---------R 
    //3.需要在tr的右区间(m, Tr]继续分治
    if(r > mid) v = max(v, query(u << 1 | 1, l, r));

    //     Tl----m----Tr
    //        L-----R 
    //2.3涵盖了这种情况
    return v;
}

//u为结点编号,更新该结点的区间最大值
void modify(int u, int x, int v) {
    if(tr[u].l == tr[u].r) tr[u].v = v;  //叶节点,递归出口
    else {
        int mid = tr[u].l + tr[u].r >> 1;
        //分治处理左右子树, 寻找x所在的子树
        if(x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        //回溯,拿子结点的信息更新父节点, 即pushup操作
        tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
    }
}

int main()
{
    //n表示树中的结点个数, last保存上一次查询的结果
    int n = 0, last = 0;  
    cin >> m >> p;

    //初始化线段树, 结点的区间最多为[1, m]。
    build(1, 1, m);  

    while(m--) 
    {
        char op;
        cin >> op;
        if(op == 'A')       //添加结点
        {
            int t;
            cin >> t;
            //在n + 1处插入
            modify(1, n + 1, ((LL)t + last) % p);
            //结点个数+1
            n++;
        }
        else
        {
            int L;
            cin >> L;
            //查询[n - L + 1, n]内的最大值,u = 1,即从根节点开始查询
            last = query(1, n - L + 1, n);
            cout << last << endl;
        }
    }

    return 0;
}


kmp

// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
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;
}

// 匹配
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)
    {
        j = ne[j];
        // 匹配成功后的逻辑
    }
}

// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;

// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int u)
{
    int t = u;
    if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}

void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}

// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);

图论

dfs

int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

bfs

queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

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

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true; // 表示点j已经被遍历过
            q.push(j);
        }
    }
}

最短路径

image-20220519142058187

朴素dijkstra算法

int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int 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[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

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


堆优化dijkstra

typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first存储距离,second存储节点编号

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

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

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

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

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


Bellman-Ford算法

int n, m;       // n表示点数,m表示边数
int dist[N];        // dist[x]存储1到x的最短路距离

struct Edge     // 边,a表示出点,b表示入点,w表示边的权重
{
    int a, b, w;
}edges[M];

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}


SPFA

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

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

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

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

spfa判断图中是否存在负环

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N], cnt[N];        // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N];     // 存储每个点是否在队列中

// 如果存在负环,则返回true,否则返回false。
bool spfa()
{
    // 不需要初始化dist数组
    // 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。

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

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

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;       // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}


FLOYD

初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}


最小生成树

kruskal

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

using namespace std;

const int N=200010,M=100010;         

int p[M];
int n,m;

struct Edge
{
    int a,b,w;

     bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[N];

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

int Kruskal()
{
    int res=0,cnt=0;//res记录最小生成树的树边权重之和,cnt记录的是全部加入到树的集合中边的数量(可能有多个集合)
    for(int i=0;i<m;i++)
    {
        int a=edges[i].a,b=edges[i].b,w=edges[i].w;
        if(find(a)!=find(b))
        /*
        具体可以参考连通块中点的数量,如果a和b已经在一个集合当中了,说明这两个点已经被一种方式连接起来了,
        如果加入a-b这条边,会导致集合中有环的生成,而树中不允许有环生成,所以一个连通块中的点的数量假设
        为x,那么里面x个节点应该是被串联起来的,有x-1条边,所以只有当a,b所属的集合不同时,才能将a-b这条
        边加入到总集合当中去
        */
        {
            p[find(a)]=p[find(b)];//将a,b所在的两个集合连接起来
            cnt++;//因为加入的是a-b的这一条边,将a,b所在的两个集合连接之后,全部集合中的边数加1
            res+=w;//加入到集合中的边的权重之和
        }
    }

    if(cnt==n-1) return res;//可以生成最小生成树
    else return 0x3f3f3f3f;//树中有n个节点便有n-1条边,如果cnt不等于n-1的话,说明无法生成有n个节点的树
}

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

    for(int i=0;i<n;i++) p[i]=i;//初始化并查集

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

    sort(edges,edges+m);//将边的权重按照大小一一排序

    int t=Kruskal();

    if(t==0x3f3f3f3f)  printf("impossible\n");
    else printf("%d\n",t);

    return 0;
}

Prim

Prim算法
dist[i]距离设置为无穷大
dist[1]=0

for i in 0..n-1

找到不在s集合中,距离s集合最近的点t
将这个点t放入集合中
利用这个点t, 更新不在集合中的点
Prim算法与Dijkstra算法的区别
Dijkstra算法是更新不在集合中的点 离起点的距离

dist[j]=min(dist[j], dist[t]+g[t][j])

Prim是更新不在集合中的点 离集合S的距离

dist[j] = min(dist[j], g[t][j])



#include<iostream>
#include<cstring>

using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int dist[N], g[N][N];
int n, m;
bool st[N];
int res = 0;

int prim() {
    memset(dist, 0x3f, sizeof dist);

    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;//
        st[t] = true;

        if (i) res += dist[t];//

        for (int j = 1; j <= n; j++) {
            dist[j] = min(dist[j], g[t][j]);//
        }
    }

    return res;

}

int main() {
    memset(g, 0x3f, sizeof g);
    cin >> n >> m;
    //
    for (int i = 0; i < m; i++) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    int t = prim();
    if (t == INF) puts("impossible");
    else cout << t << endl;
}

作者:松鼠爱葡萄
链接:https://www.acwing.com/solution/content/14120/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2022-10-12 00:12  Sakurakio  阅读(49)  评论(0)    收藏  举报