((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第2天】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有74天

news2024/11/18 15:40:45

🏆🏆🏆🏆🏆🏆🏆
欢迎观看我的博客,如有问题交流,欢迎评论区留言,一定尽快回复!(大家可以去看我的专栏,是所有文章的目录)
 
文章字体风格:
红色文字表示:重难点✔★
蓝色文字表示:思路以及想法✔★
 
如果大家觉得有帮助的话,感谢大家帮忙
点赞!收藏!转发!

 
我的qq号是:1210931886,欢迎大家加群,一起学习,互相交流,共同进步🎉🎉✨✨
🥇🥇🥇🥇🥇🥇🥇

蓝桥杯系列,为大家提供

  1. 做题全集,备战蓝桥杯,就做这个系列的题即可
  2. 一个大概的做题规划——大家最好在此基础上提前两个月准备

备战蓝桥杯就刷这些题
第一天博客链接
第二天博客链接

备战(蓝桥杯)算法竞赛-第2天

        • 3. 差分(9分钟)
            • 二刷总结
        • 4. 差分矩阵(12分钟)
  • 六、双指针
        • ★ 1. 最长连续不重复子序列(20分钟)
            • 二刷总结(以空间换时间)
        • 2. 数组元素的目标和(7分钟)
        • 3. 判断子序列(8分钟)
  • 七、二进制
        • 1. 位运算算法(2分钟)
            • 返回n的最后一位1:lowbit(n) = n & -n
            • 一共有多少1 : while n = n ^(n & -n)或者 n -= n & -n
  • 八、离散化
        • ★1. 区间和(20分钟)
            • ★二刷总结
  • 九、区间合并
        • 1. 区间合并(7分钟)
  • 1. 单链表模板
        • 1. 单链表(7分钟)
  • 2. 双链表模板
        • 1. 双链表
  • 3. 模拟栈
        • 1. 模拟栈
        • 2. 表达式求值(20分钟)
  • 4. 队列 tt = -1,hh = 0;
        • 1. 模拟队列
  • 5. 单调栈
        • 1. 单调栈(4分钟)
  • 6. 单调队列
        • 1. 滑动窗口例题(10分钟)
  • 7. KMP
        • 1. KMP字符串(10分钟)
          • 二刷体会
            • ★三刷体会 ne表示算上第一个和最后一个的前缀后缀相等值
  • 8. Trie树
        • 1. Trie字符串统计(20分钟)
            • 二刷总结
        • ★2. 最大异或对
            • ★二刷总结
  • 9. 并查集 find merge
      • 1. 合并集合(5分钟)
      • 2. 连通块中点的数量(每个集合有多少个元素)
      • ★3. 食物链
          • 法一: x,x+n,x+n+n merge(f[x+n],f[x])
            • 二刷总结
          • 法二:将有关系的都存储在一个部落,用到根节点的距离表示关系
            • 二刷总结
  • 10. 堆 ✔ ✔
      • 模板
      • 1. 堆排序 ✔ ✔
      • 2. 模拟堆 ✔ ✔
  • 11. 哈希表 ✔ ✔
        • 模板
      • 例1. 模拟散列表 ✔ ✔
          • 拉链法代码(链表)
          • 开放寻址法代码(有空位就存,空位用null=0x3f3f3f3f)
      • ★例2. 字符串哈希 ✔ ✔
          • 二刷思路

3. 差分(9分钟)

二刷总结
  1. 由差分求s时,是要有数据连续性的,前面的改变了,要保证对应后面的也跟着

原题链接

#include<iostream>

using namespace std;

const int N = 1e5+10;

int a[N],s[N];

int main()
{
    int n,m;
    cin >> n >> m;
    
    for(int i = 1; i <= n; i++) cin >> s[i];
    
    for(int i = 1; i <= n; i++)
    {
        a[i] = s[i] - s[i-1];
    }
    
    while(m--)
    {
        int l,r,c;
        cin >> l >> r >> c;
        a[l] += c;
        a[r+1] -= c;
    }
    
    for(int i = 1; i <= n; i++)
    {
        a[i] = a[i] + a[i-1];
        cout << a[i] << ' ';
    }
    
    return 0;
}

4. 差分矩阵(12分钟)

原题链接

#include<iostream>

using namespace std;

const int N = 1010;

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

int main()
{
    int n,m,q;
    cin >> n >> m >> q;
    
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++) cin >> s[i][j];
        
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            a[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1];

    while(q--)
    {
        int x1,y1,x2,y2,c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        a[x1][y1] += c;
        a[x1][y2+1] -= c;
        a[x2+1][y1] -= c;
        a[x2+1][y2+1] += c;
    }
            
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            a[i][j] = a[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++)
        {
            cout << a[i][j] << ' ';
            if(j==m)
            cout << endl;
        }
    
    return 0;
}

六、双指针

★ 1. 最长连续不重复子序列(20分钟)

二刷总结(以空间换时间)

原题链接

#include <iostream>

using namespace std;

const int N = 100010;

int n;
int q[N], s[N];

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

    int res = 0;
    for (int i = 0, j = 0; i < n; i ++ )
    {
        s[q[i]] ++ ;
        while (j < i && s[q[i]] > 1) s[q[j ++ ]] -- ;
        res = max(res, i - j + 1);
    }

    cout << res << endl;

    return 0;
}

2. 数组元素的目标和(7分钟)

原题链接

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

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

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 (j >= 0 && a[i] + b[j] == x) cout << i << ' ' << j << endl;
    }

    return 0;
}

3. 判断子序列(8分钟)

原题链接

#include<iostream>

using namespace std;

const int N = 1e5+10;

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

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= m; i++) cin >> b[i];
    
    int i,j;
    for(i = 1, j = 1;  i <= m; i++)
    {
        if(a[j] == b[i])
            j++;
        if(j==n+1)
            break;
    }
    //cout << j << ' ' << n << endl;
    if(j==n+1)
        cout << "Yes";
    else
        cout << "No";
    
    return 0;
}

七、二进制

最全二进制算法总结

1. 位运算算法(2分钟)

返回n的最后一位1:lowbit(n) = n & -n
一共有多少1 : while n = n ^(n & -n)或者 n -= n & -n

原题链接

#include<iostream>
using namespace std;


int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int x;
        cin >> x;
        int res = 0;
        while(x)
        {
            x = x ^ (x&-x);
            res++;
        }
        cout << res << ' ';
    }
    return 0;
}

八、离散化

sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());

★1. 区间和(20分钟)

原题链接

★二刷总结
  1. 用数组存储数组边界值,如需要用二分查找
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

typedef pair<int,int> PII;

const int N = 300010;
int a[N],s[N];

vector<int> alls;
vector<PII> add;
int find(int x)
{
    int l = 0,r = alls.size()-1;
    while(l<r)
    {
        int mid = l + r >> 1;
        if(alls[mid]>=x) r = mid;
        else l = mid + 1;
    }
    return l+1;
}

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

    for(int i = 0; i < n; i++)
    {
        int x,c;
        cin >> x >> c;
        add.push_back({x,c});
        alls.push_back(x);
    }
    
    vector<PII> query;
    for(int i = 0; i < m; i++)
    {
        int l,r;
        cin >> l >> r;
        query.push_back({l,r});
        alls.push_back(l);
        alls.push_back(r);
    }
    
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()),alls.end());
    
    
    for(auto item : add)
    {
        int x = find(item.first);
        a[x] += item.second;
    }
    
    for(int i = 1; i <= alls.size(); i++) s[i] = a[i] + s[i-1];
    
    for(auto item : query)
    {
        cout << s[find(item.second)] - s[find(item.first)-1] << endl;
    }
    
    return 0;
}

九、区间合并

1. 区间合并(7分钟)

贪心做法

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

typedef pair<int,int> PII;

vector<PII> a;

int main()
{
    int n;
    cin >> n;
    
    int l,r;
    for(int i = 0; i < n; i++) cin >> l >> r, a.push_back({l,r});
    
    sort(a.begin(),a.end());
    
    int ed = -0x3f3f3f3f;
    
    int cnt = 0;
    for(auto x : a)
    {
        if(ed<x.first)
        {
            cnt++;
            ed = max(ed,x.second);
        }
        else
        {
            ed = max(ed,x.second);
        }
    }
    cout << cnt;
    return 0;
}

数据结构

        • 3. 差分(9分钟)
            • 二刷总结
        • 4. 差分矩阵(12分钟)
  • 六、双指针
        • ★ 1. 最长连续不重复子序列(20分钟)
            • 二刷总结(以空间换时间)
        • 2. 数组元素的目标和(7分钟)
        • 3. 判断子序列(8分钟)
  • 七、二进制
        • 1. 位运算算法(2分钟)
            • 返回n的最后一位1:lowbit(n) = n & -n
            • 一共有多少1 : while n = n ^(n & -n)或者 n -= n & -n
  • 八、离散化
        • ★1. 区间和(20分钟)
            • ★二刷总结
  • 九、区间合并
        • 1. 区间合并(7分钟)
  • 1. 单链表模板
        • 1. 单链表(7分钟)
  • 2. 双链表模板
        • 1. 双链表
  • 3. 模拟栈
        • 1. 模拟栈
        • 2. 表达式求值(20分钟)
  • 4. 队列 tt = -1,hh = 0;
        • 1. 模拟队列
  • 5. 单调栈
        • 1. 单调栈(4分钟)
  • 6. 单调队列
        • 1. 滑动窗口例题(10分钟)
  • 7. KMP
        • 1. KMP字符串(10分钟)
          • 二刷体会
            • ★三刷体会 ne表示算上第一个和最后一个的前缀后缀相等值
  • 8. Trie树
        • 1. Trie字符串统计(20分钟)
            • 二刷总结
        • ★2. 最大异或对
            • ★二刷总结
  • 9. 并查集 find merge
      • 1. 合并集合(5分钟)
      • 2. 连通块中点的数量(每个集合有多少个元素)
      • ★3. 食物链
          • 法一: x,x+n,x+n+n merge(f[x+n],f[x])
            • 二刷总结
          • 法二:将有关系的都存储在一个部落,用到根节点的距离表示关系
            • 二刷总结
  • 10. 堆 ✔ ✔
      • 模板
      • 1. 堆排序 ✔ ✔
      • 2. 模拟堆 ✔ ✔
  • 11. 哈希表 ✔ ✔
        • 模板
      • 例1. 模拟散列表 ✔ ✔
          • 拉链法代码(链表)
          • 开放寻址法代码(有空位就存,空位用null=0x3f3f3f3f)
      • ★例2. 字符串哈希 ✔ ✔
          • 二刷思路

1. 单链表模板

1. 单链表(7分钟)

原题链接

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

2. 双链表模板

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

1. 双链表

原题链接

#include <iostream>

using namespace std;

const int N = 100010;

int m;
int e[N], l[N], r[N], idx;

// 在节点a的右边插入一个数x
void insert(int a, int x)
{
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx ++ ;
}

// 删除节点a
void remove(int a)
{
    l[r[a]] = l[a];
    r[l[a]] = r[a];
}

int main()
{
    cin >> m;

    // 0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;

    while (m -- )
    {
        string op;
        cin >> op;
        int k, x;
        if (op == "L")
        {
            cin >> x;
            insert(0, x);
        }
        else if (op == "R")
        {
            cin >> x;
            insert(l[1], x);
        }
        else if (op == "D")
        {
            cin >> k;
            remove(k + 1);
        }
        else if (op == "IL")
        {
            cin >> k >> x;
            insert(l[k + 1], x);
        }
        else
        {
            cin >> k >> x;
            insert(k + 1, x);
        }
    }

    for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
    cout << endl;

    return 0;
}

3. 模拟栈

1. 模拟栈

原题链接

#include <iostream>

using namespace std;

const int N = 100010;

int m;
int stk[N], tt;

int main()
{
    cin >> m;
    while (m -- )
    {
        string op;
        int x;

        cin >> op;
        if (op == "push")
        {
            cin >> x;
            stk[ ++ tt] = x;
        }
        else if (op == "pop") tt -- ;
        else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;
        else cout << stk[tt] << endl;
    }

    return 0;
}

2. 表达式求值(20分钟)

原题链接

#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <unordered_map>

using namespace std;

stack<int> num;
stack<char> op;

void eval()
{
    auto b = num.top(); num.pop();
    auto a = num.top(); num.pop();
    auto c = op.top(); op.pop();
    int x;
    if (c == '+') x = a + b;
    else if (c == '-') x = a - b;
    else if (c == '*') x = a * b;
    else x = a / b;
    num.push(x);
}

int main()
{
    unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
    string str;
    cin >> str;
    for (int i = 0; i < str.size(); i ++ )
    {
        auto c = str[i];
        if (c>='0' && c <='9')
        {
            int x = 0, j = i;
            while (j < str.size() && isdigit(str[j]))
                x = x * 10 + str[j ++ ] - '0';
            i = j - 1;
            num.push(x);
        }
        else if (c == '(') op.push(c);
        else if (c == ')')
        {
            while (op.top() != '(') eval();
            op.pop();
        }
        else
        {
            while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();
            op.push(c);
        }
    }
    while (op.size()) eval();
    cout << num.top() << endl;
    return 0;
}

4. 队列 tt = -1,hh = 0;

1. 模拟队列

原题链接

#include <iostream>

using namespace std;

const int N = 100010;

int m;
int q[N], hh, tt = -1;

int main()
{
    cin >> m;

    while (m -- )
    {
        string op;
        int x;

        cin >> op;
        if (op == "push")
        {
            cin >> x;
            q[ ++ tt] = x;
        }
        else if (op == "pop") hh ++ ;
        else if (op == "empty") cout << (hh <= tt ? "NO" : "YES") << endl;
        else cout << q[hh] << endl;
    }

    return 0;
}

5. 单调栈

常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
    while (tt && check(stk[tt], i)) tt -- ;
    stk[ ++ tt] = i;
}

1. 单调栈(4分钟)

原题链接

原题链接

#include <iostream>

using namespace std;

const int N = 100010;

int stk[N], tt;

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        scanf("%d", &x);
        /*读进一个数,和栈里的数比较,如果栈里的数违
        规了,那么出栈*/
        while (tt && stk[tt] >= x) tt -- ;
        //如何栈里的数,不满足,那么输出-1
        if (!tt) printf("-1 ");
        //如果满足 那么输出栈
        else printf("%d ", stk[tt]);
        //输入该值,去比较下一个	
        stk[ ++ tt] = x;
    }

    return 0;
}

6. 单调队列

常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
    while (hh <= tt && check_out(q[hh])) hh ++ ;  // 判断队头是否滑出窗口
    while (hh <= tt && check(q[tt], i)) tt -- ;
    q[ ++ tt] = i;
}

1. 滑动窗口例题(10分钟)

原题链接
原题链接

#include<iostream>
#include<deque>

using namespace std;

const int N = 1e6+10;

int a[N];
deque<int> q;

int main()
{
    int n,k;
    cin >> n >> k;
    
    for(int i = 1; i <= n; i++) cin >> a[i];
    
    for(int i = 1; i <= n; i++)
    {
        if(q.size() && q.front()+k==i)q.pop_front();
        while(q.size() && a[q.back()]>=a[i])q.pop_back();
        q.push_back(i);
        if(i>=k)
            cout << a[q.front()] << ' ';
    }
    cout << endl;
    while(q.size())q.pop_back();
    
    for(int i = 1; i <= n; i++)
    {
        if(q.size() && q.front()+k==i)q.pop_front();
        while(q.size() && a[q.back()]<=a[i])q.pop_back();
        q.push_back(i);
        if(i>=k)
            cout << a[q.front()] << ' ';
    }
    return 0;
}

7. 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];
        // 匹配成功后的逻辑
    }
}

1. KMP字符串(10分钟)

原题链接

二刷体会
  1. next数组存储的是,但j不匹配了,j调整到匹配位置,然后j+1和i去比较
  2. 所以j就需要 = i - 1
  3. 当角标从0开始 j 就得=-1,角标从1开始,j = 0
★三刷体会 ne表示算上第一个和最后一个的前缀后缀相等值
#include <iostream>

using namespace std;

const int N = 100010, M = 1000010;

int n, m;
int ne[N];
char s[M], p[N];

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;

    for (int i = 2, j = 0; i <= n; 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 <= m; i ++ )
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j ++ ;
        if (j == n)
        {
            printf("%d ", i - n);
            j = ne[j];
        }
    }

    return 0;
}

8. Trie树

1. Trie字符串统计(20分钟)

二刷总结

一行只存储一个数据
原题链接

#include<iostream>

using namespace std;

const int N = 1e5+10;

int a[26][N],idx;
int cnt[N];

int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        char ch;
        cin >> ch;
        
        if(ch == 'I')
        {
            string s;
            cin >> s;
            int p = 0;
            for(int i = 0; i < s.size();i++)
            {
                if(a[s[i] - 'a'][p])
                {
                    p = a[s[i] - 'a'][p];
                }
                else
                {
                    a[s[i] - 'a'][p] = ++idx;
                    p = a[s[i] - 'a'][p];
                }
            }
            cnt[p]++;
        }
        else
        {
            string s;
            cin >> s;
            int p = 0;
            for(int i = 0; i < s.size(); i++)
            {
                if(a[s[i]-'a'][p])
                {
                    p = a[s[i]-'a'][p];
                }
                else
                {
                    cout << 0 << endl;
                    break;
                }
                if(i==s.size()-1)
                {
                    cout << cnt[p] << endl;
                }
            }
        }
    }
    
    
    return 0;
}

★2. 最大异或对

原题链接

★二刷总结

正常思路:

  1. 两个循环遍历,时间复杂度(N2
for(int i = 0; i < n; i++)
{
	for(int j = 0; j < n; j++)
	{
		res = max(res,a[i]^a[j]);
	}
}	
  1. 优化:创建一个Trie树,针对一个数,去遍历Trie树
    (怎么想到用Trie树去优化呢?Trie树可以存储组合性质的信息)
#include<iostream>
#include<algorithm>
using namespace std;
int const N=100010,M=31*N;

int n;
int a[N];
int son[M][2],idx;
//M代表一个数字串二进制可以到多长

void insert(int x)
{
    int p=0;  //根节点
    for(int i=30;i>=0;i--)
    {
        int u=x>>i&1;   /取X的第i位的二进制数是什么  x>>k&1(前面的模板)
        if(!son[p][u]) son[p][u]=++idx; ///如果插入中发现没有该子节点,开出这条路
        p=son[p][u]; //指针指向下一层
    }
}
int search(int x)
{
    int p=0;int res=0;
    for(int i=30;i>=0;i--)
    {                               
    ///从最大位开始找
        int u=x>>i&1;
        if(son[p][!u]) 如果当前层有对应的不相同的数
        {   ///p指针就指到不同数的地址

          p=son[p][!u];
          res=res*2+1;
             ///*2相当左移一位  然后如果找到对应位上不同的数res+1 例如    001
        }                                                       ///       010 
        else                                                      --->011                                                                           //刚开始找0的时候是一样的所以+0    到了0和1的时候原来0右移一位,判断当前位是同还是异,同+0,异+1
        {
            p=son[p][u];
            res=res*2+0;
        }
    }
    return res;
}
int main(void)
{
    cin.tie(0);
    cin>>n;
    idx=0;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        insert(a[i]);
    }
    int res=0;
    for(int i=0;i<n;i++)
    {   
        res=max(res,search(a[i]));  ///search(a[i])查找的是a[i]值的最大与或值
    }
    cout<<res<<endl;
}

9. 并查集 find merge

1. 合并集合(5分钟)

原题链接
原题链接

#include<iostream>

using namespace std;

const int N=100010;
int p[N];//定义多个集合

int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    /*
    经上述可以发现,每个集合中只有祖宗节点的p[x]值等于他自己,即:
    p[x]=x;
    */
    return p[x];
    //找到了便返回祖宗节点的值
}

int main()
{
    int n,m;
    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);
        if(*op=='M') p[find(a)]=find(b);//集合合并操作
        else
        if(find(a)==find(b))
        //如果祖宗节点一样,就输出yes
        printf("Yes\n");
        else
        printf("No\n");
    }
    return 0;
}

2. 连通块中点的数量(每个集合有多少个元素)

原题链接

#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int p[N], cnt[N];

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

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

    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        cnt[i] = 1;
    }

    while (m -- )
    {
        string op;
        int a, b;
        cin >> op;

        if (op == "C")
        {
            cin >> a >> b;
            a = find(a), b = find(b);
            if (a != b)
            {
                p[a] = b;
                cnt[b] += cnt[a];
            }
        }
        else if (op == "Q1")
        {
            cin >> a >> b;
            if (find(a) == find(b)) puts("Yes");
            else puts("No");
        }
        else
        {
            cin >> a;
            cout << cnt[find(a)] << endl;
        }
    }

    return 0;
}

★3. 食物链

原题链接

法一: x,x+n,x+n+n merge(f[x+n],f[x])

思路:

  1. 因为有三种物种,A吃B,B吃C,C吃A
  2. 如果我们用一个数组存储,那么比如1吃2,那么我们让2的角标处的值标记成1,如果3吃2,那怎么标记?一个数组指定标记不过来。
  3. 那么我们想用三个数组存储,其实也存储不过来,因为角标就那么几个,
  4. 最好的方法就是,用x,x+n,x+n+n来表示
    比如1吃2,那么就可能有三种情况,
    A类中的1吃B类的2 : fa[1] = fa[2+n+n]
    B类中的1吃C类的2 : fa[1+n] = fa[2]
    C类中的1中A类的2 : fa[1+n+n] = fa[2+n];
    这样的话,就会有3*n个角标,就可以充分表达
    A中的1吃B中的2(B中的2用2+n表示)
    这样的话就不会出现数字冲突

A吃B
则让f[A] = B

二刷总结

a吃b
f[a+n] = f[b]
或者
f[b+n] = f[a]

/*

*/

#include <bits/stdc++.h>
using namespace std;
int fa[200000];
int n,m,k,x,y,ans;
int get(int x)
{
    if(x==fa[x]) 
        return x;
    return fa[x]=get(fa[x]);
}
void merge(int x,int y)
{
    fa[get(x)]=get(y);
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=3*n;i++) 
        fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&k,&x,&y);
        if(x>n || y>n) 
            ans++;
        else if(k==1)
        {
            if(get(x)==get(y+n) || get(x)==get(y+n+n)) //如果x,y是同类,但是x是y的捕食中的动物,或者x是y天敌中的动物,那么错误.
                ans++;
            else
            {
                merge(x,y);
                merge(x+n,y+n);
                merge(x+n+n,y+n+n);
            }
        }
        else
        {
            if(x==y || get(x)==get(y) || get(x)==get(y+n)) //x就是y,或者他们是同类,再或者是y的同类中有x
                ans++;//都是假话
            else
            {
                merge(x,y+n+n);//y的捕食域加入x
                merge(x+n,y);//x的天敌域加入y
                merge(x+n+n,y+n);//x的捕食域是y的同类域.
            }
        }
    }
    cout<<ans<<endl;
}
法二:将有关系的都存储在一个部落,用到根节点的距离表示关系
二刷总结
  1. X吃Y 让x的祖宗等于y的祖宗 或者 y的祖宗等于x的祖宗都可以
  2. find查找函数中,具有压缩路径的作用,所以在写的时候,先找到此根节点,然后依次压缩,如下代码
int find(int x)
{
    if (p[x] != x)
    {
        int t = find(p[x]);
        d[x] += d[p[x]];
        p[x] = t;
    }
    return p[x];
}

不可以如下代码

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

  1. 在查询合并过程中,比如查询x的父节点,应该用一个变量记录下来,不能多次find,不然找不到x的原来父节点了
  2. 初始化 p[i] = i; d[i] = 0;
  3. 合并的时候,画图即可明白彼此的距离

具体过程如下:

#include <iostream>

using namespace std;

const int N = 50010;

int n, m;
int p[N], d[N];

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

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

    for (int i = 1; i <= n; i ++ ) p[i] = i;

    int res = 0;
    while (m -- )
    {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);

        if (x > n || y > n) res ++ ;
        else
        {
            int px = find(x), py = find(y);
            if (t == 1)
            {
                if (px == py && (d[x] - d[y]) % 3) res ++ ;
                else if (px != py)
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
            else
            {
                if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;
                else if (px != py)
                {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];
                }
            }
        }
    }

    printf("%d\n", res);

    return 0;
}

10. 堆 ✔ ✔

模板

// 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);
  1. 向下调整
    法一:如果孩子节点 小于当前节点 那么交换 然后递归
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);
    }
}

法二:parent = child * 2 直到 超出范围

    public void createHeap() {
        for (int parent = (usedSize-1-1) / 2; parent >= 0 ; parent--) {
            shiftDown(parent,usedSize);
        }
    }

    /**
     * 实现 向下调整
     * @param parent 每棵子树的根节点的下标
     * @param len 每棵子树的结束位置
     */

    private void shiftDown(int parent ,int len) {
        int child = 2 * parent + 1;
        //最起码是有左孩子
        while (child < len) {
            //判断 左孩子 和 右孩子  谁最大,前提是  必须有  右孩子
            if(child+1 < len && elem[child] < elem[child+1]) {
                child++;//此时 保存了最大值的下标
            }
            if(elem[child] > elem[parent]) {
                swap(elem,child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

    private void swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

堆的操作

  1. 删除最值
  1. 因为最小值,位于数组的最前端,所以我们可以让最前端等于最后端的值,(此时堆的最小值已经没了)
  2. 但是呢,堆里出现了两个最前端的值,所以需要再–sz删除最后段的值(不要怕,最后段的值,已经保存到最前端的结点上,)
  3. 最后把最顶端的点 用down
  1. 修改值(改,先down,后up)

在这里插入图片描述

在这里插入图片描述

孩子和父亲的关系
在这里插入图片描述

数组建堆
在这里插入图片描述

1. 堆排序 ✔ ✔

原题链接

在这里插入图片描述

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int h[N], cnt;

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

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

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

    while (m -- )
    {
        printf("%d ", h[1]);
        h[1] = h[cnt -- ];
        down(1);
    }

    puts("");
    return 0;
}

2. 模拟堆 ✔ ✔

原题链接

在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 100010;

int h[N], ph[N], hp[N], cnt;

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 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && 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;
    }
}

int main()
{
    int n, m = 0;
    scanf("%d", &n);
    while (n -- )
    {
        char op[5];
        int k, x;
        scanf("%s", op);
        if (!strcmp(op, "I"))
        {
            scanf("%d", &x);
            cnt ++ ;
            m ++ ;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
        else if (!strcmp(op, "DM"))
        {
            heap_swap(1, cnt);
            cnt -- ;
            down(1);
        }
        else if (!strcmp(op, "D"))
        {
            scanf("%d", &k);
            k = ph[k];
            heap_swap(k, cnt);
            cnt -- ;
            up(k);
            down(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }

    return 0;
}

11. 哈希表 ✔ ✔

模板

(1) 拉链法
    int h[N], e[N], ne[N], idx;

    // 向哈希表中插入一个数
    void insert(int x)
    {
        int k = (x % N + N) % N;
        e[idx] = x;
        ne[idx] = h[k];
        h[k] = idx ++ ;
    }

    // 在哈希表中查询某个数是否存在
    bool find(int x)
    {
        int k = (x % N + N) % N;
        for (int i = h[k]; i != -1; i = ne[i])
            if (e[i] == x)
                return true;
 
        return false;
    }

(2) 开放寻址法
    int h[N];

    // 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
    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;
    }

例1. 模拟散列表 ✔ ✔

原题链接

在这里插入图片描述

拉链法代码(链表)
  1. N取大于范围的第一个质数
  2. k = (x % N + N) % N; (%N 为了避免超级大的值 + N 为了避免出现负数 再%N为了正数+N超出范围)
  3. memset(h, -1, sizeof h);
#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;
}
开放寻址法代码(有空位就存,空位用null=0x3f3f3f3f)
  1. 大于2倍的第一个质数
#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;
}

★例2. 字符串哈希 ✔ ✔

原题链接

二刷思路
  1. 把一段字符串转成一段数字,便可以直接比较相等
  2. 怎么转?进制大于26
  3. 样例没找全 p[r-l+1] 这个点没找到
#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5+5,P = 131;//131 13331
ULL h[N],p[N];

// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或  13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
ULL query(int l,int r){
    return h[r] - h[l-1]*p[r-l+1];
}
int main(){
    int n,m;
    cin>>n>>m;
    string x;
    cin>>x;

    //字符串从1开始编号,h[1]为前一个字符的哈希值
    p[0] = 1;
    h[0] = 0;
    for(int i=0;i<n;i++){
        p[i+1] = p[i]*P;            
        h[i+1] = h[i]*P +x[i];      //前缀和求整个字符串的哈希值
    }

    while(m--){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(query(l1,r1) == query(l2,r2)) printf("Yes\n");
        else printf("No\n");

    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/195158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaScript速成之路】JavaScript变量

&#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f525;系列专栏&#xff1a;【JavaScript速成之路】 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 文章目录前言1&#xff0c;JavaScript变量1.1&#xff0c;变量的含义1.2&#xff0c;变量…

C语言指针变量的定义和使用

数据在内存中的地址也称为指针&#xff0c;如果一个变量存储了一份数据的指针&#xff0c;我们就称它为指针变量。在C语言中&#xff0c;允许用一个变量来存放指针&#xff0c;这种变量称为指针变量。指针变量的值就是某份数据的地址&#xff0c;这样的一份数据可以是数组、字符…

微盟全链路压测:如何帮助电商业务实现 10 倍性能提升?

一分钟精华速览 全链路压测之所以被誉为电商大促备战的 “核武器” &#xff0c;是因为它基于实际的生产业务场景、系统环境&#xff0c;模拟海量的用户请求和数据对整个业务链进行压力测试&#xff0c;能真实反映系统的状况&#xff0c;对系统风险和瓶颈真正做到心中有数。 …

Flutter 学习 - Dart 语言基础

文章目录前言一、Dart 概述Dart 重要的概念二、变量与基本数据类型三、函数四、运算符五、流程控制语句六、异常处理七、面向对象构造函数继承类抽象类枚举类型八、泛型九、库的使用引用库指定库前缀引用库的一部分总结技巧1. 安全调用2. 设置默认值3. 简化判断前言 Dart 作为…

【RabbitMQ】快速入门学习MQ

目录 1.初识MQ 1.1.同步和异步通讯 1.1.1.同步通讯 1.1.2.异步通讯 1.2.技术对比&#xff1a; 2.快速入门 2.1.安装RabbitMQ 2.2.RabbitMQ消息模型 2.3.导入Demo工程 2.4.入门案例 2.4.1.publisher实现 2.4.2.consumer实现 2.5.总结 1.初识MQ 1.1.同步和异步通讯…

最全面的SpringBoot教程(二)——SpringBoot配置文件

前言 本文为SpringBoot配置文件相关内容介绍&#xff0c;下边将对配置文件分类&#xff0c;yaml基本语法&#xff0c;yaml数据格式&#xff0c;获取数据&#xff0c;profile-运维&#xff0c;项目内部配置文件加载顺序&#xff0c;项目外部配置文件加载顺序等进行详尽介绍~ &a…

Golang实现微信公众号后台

最近在学习Golang&#xff0c;写了个微信公众号项目练练手。 一、开发前准备 1、注册微信公众号 百度搜索微信公众号进入官网&#xff0c;注册一个订阅号&#xff0c;其他信息按要求填写即可。 注册完成后进入个人公众号主页&#xff0c;下拉至设置与开发 点击基本配置&…

π130E31 3.0kV rms 隔离电压200Mbps三通道数字隔离器代替Si8635BC-B-IS1

π130E31 3.0kV rms 隔离电压200Mbps三通道数字隔离器代替Si8635BC-B-IS1&#xff0c;具有出色的性能特 征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器 产品。 智能分压技术(iDivider技术)是荣湃半导体发明的新一代数字 隔离器技术。智能分压技术(iDivide…

【强训】Day08

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录一、选择二、编程&#xff08;比较简单&#xff09;1. 两种排序方法2. 求最小公倍数&#xff08;主要注意辗转相除法就行&#xff09;答案1. 选择2. 编程普通小孩也要热爱生活&#xff01; 一、选择 以下选项中&am…

Mybatis_plus的一些介绍

这里写目录标题建立数据库utf8mb4是什麽&#xff1f;为什么要用这个呢&#xff1f;utf8mb4_unicode_ci、utf8mb4_general_ci的区别总结数据库连接配置文件创建实体类模型sql日志的输出创建springboot的测试类批量查询建立数据库 注意一下&#xff0c;这个表应该这么去建 utf8…

操作系统权限提升(九)之系统错误配置-泄露敏感信息提权

系列文章 操作系统权限提升(一)之操作系统权限介绍 操作系统权限提升(二)之常见提权的环境介绍 操作系统权限提升(三)之Windows系统内核溢出漏洞提权 操作系统权限提升(四)之系统错误配置-Tusted Service Paths提权 操作系统权限提升(五)之系统错误配置-PATH环境变量提权 操作…

一刷代码随想录——栈和队列

1 理论基础栈的底层实现可以是vector&#xff0c;deque&#xff0c;list 都是可以的&#xff0c; 主要就是数组和链表的底层实现。我们常用的SGI STL&#xff0c;如果没有指定底层实现的话&#xff0c;默认是以deque为缺省情况下栈的底层结构。栈不提供走访功能&#xff0c;也不…

SAP ABAP——SMARTFORMS(一)【SF概要及文本编辑器】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计专业大二本科在读&#xff0c;阿里云社区专家博主&#xff0c;华为云社区云享专家&#xff0c;CSDN SAP应用技术领域新兴创作者。   在学习工…

利用 Addax 异构迁移数据到 Databend

作者&#xff1a;邰翀 (https://github.com/TCeason) Databend 研发工程师 现在互联网应用越来越复杂&#xff0c;每个公司都会有多种多样的数据库。通常是用最好的硬件来跑 OLTP&#xff0c;甚至还在 OLTP 中进行分库分表来足业务&#xff0c;这样对于一些分析&#xff0c;聚…

微信小程序封装wx.request请求

对微信小程序的印象我还停留在2年前刚入行的时候&#xff0c;那是还不懂什么是Promise&#xff0c;只知道小程序发请求有时候要在success回调中嵌套好多层(后来我才知道这叫回调地狱)。最近刚好有个小程序的项目交给我发开发&#xff0c;加上如今的我自认为对Promise掌握的还可…

Python接口项目实战篇(1)读取xlsx中账户密码,unittest框架实现通过requests接口post登录网站请求,JSON判断登录是否成功

Python接口项目实战篇&#xff08;1&#xff09;读取xlsx中账户密码&#xff0c;unittest框架实现通过requests接口post登录网站请求&#xff0c;JSON判断登录是否成功实现功能描述1.首先获取到接口谷歌浏览器中获取接口信息fiddler里面抓取接口信息2.创建一个xlsx文档3.导入我…

List-反向迭代器

List List接口使用 List&#xff1a;双向带头循环的链表&#xff0c;不支持随机访问&#xff0c;排序就是一个大问题 当大量的插入数据的时候就体现出了优势。 在任意位置以O(1)的时间复杂度插入数据. 只有一种遍历方式就是迭代器&#xff0c;因为他的物理结构是不连续的无…

05_xml

目录0、文档声明1.XML 简介2、xml 的作用&#xff1f;3、xml 语法3.1、文档声明3.2、xml 注释3.3、元素&#xff08;标签&#xff09;3.4、xml 属性3.5、语法规则&#xff1a;3.5.1&#xff09;所有 XML 元素都须有关闭标签&#xff08;也就是闭合&#xff09;3.5.2&#xff09…

uniapp:常用跨端业务组件(ts版本)

插件内组件包含以下功能&#xff1a; 自定义状态栏组件(ZoNavBar)上拉加载状态组件(ZoLoading)弹窗组件(ZoPopup)搜索组件(ZoSearchBar)空数据组件(ZoEmpty)tab标签组件(ZoTabs)tab下拉筛选组件(ZoSelectTabs)底部导航组件(ZoTabBar)cell导航组件(ZoCell) 10.标题内容组件(ZoT…

iptables端口复用后门、sslh

iptables端口复用 创建端口复用链 创建端口复用规则将流量转到22端口 开启开关&#xff0c;接收到一个长为1139的icmp包&#xff0c;则将来源ip添加到LETMEIN表中 如果syn包来源ip处于letmein列表中&#xff0c;则跳转到LETMEIN链处理&#xff0c;有效时间为3600秒 开启复…