算法基础-1

一 基础算法(上)

1.模拟

1.1多项式输出

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
    int n; 
    cin >> n;
    // 循环次数,从最高次n遍历到0次
    for(int i = n; i >= 0; i--)
    {
        int a; 
        cin >> a;
        if(a == 0) continue; // 处理系数为 0 的情况
        
        // 1. 符号
        if(a < 0) 
            cout << '-';
        else
        {
            if(i != n) cout << '+';
        }
        
        // 2. 数字
        a = abs(a);
        if(a != 1 || (a == 1 && i == 0)) //末项1*x^0=1
            cout << a;
        
        // 3. 次数
        if(i == 0) 
            continue;
        else if(i == 1) 
            cout << 'x';
        else 
            cout << "x^" << i;
    }
    return 0;
}

1.2蛇形方阵

因为这个题目它是从左上角开始的 所以需要在左上角为(0,0)

cpp 复制代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 15;
// 定义 右,下,左,上 四个方向
int dx[] = {0, 1, 0, -1}; 
int dy[] = {1, 0, -1, 0}; 
int arr[N][N];

int main()
{
    int n; cin >> n;
    // 模拟填数过程
    int x = 1, y = 1; // 初始位置
    int cnt = 1;      // 当前位置要填的数
    int pos = 0;      // 当前的方向
    while(cnt <= n * n)
    {
        arr[x][y] = cnt;
        // 计算下一个位置
        int a = x + dx[pos], b = y + dy[pos];
        // 判断是否越界或者已经填过数字
        if(a < 1 || a > n || b < 1 || b > n || arr[a][b])
        {
            // 换方向
            pos = (pos + 1) % 4; //进行循环 当pos为3的时候会=0
            a = x + dx[pos], b = y + dy[pos];
        }
        x = a, y = b;
        cnt++;
    }
    // 打印矩阵
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            printf("%3d", arr[i][j]);
        }
        puts("");
    }
    return 0;
}

1.3字符串的展开

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

int p1, p2, p3, n;
string s;
string ret;

// 判断是否是数字字符
bool isdig(char ch)
{
    return ch >= '0' && ch <= '9';
}

// 判断是否是小写字母
bool islet(char ch)
{
    return ch >= 'a' && ch <= 'z';
}

// 把 [left, right] 之间的字符展开
// left, right 这两个字符是不做处理
void add(char left, char right)
{
    string t;
    // 遍历中间的字符
    for(char ch = left + 1; ch < right; ch++) //left和right不做展示
    {
        char tmp = ch;
        // 处理 p1
        if(p1 == 2 && islet(tmp)) tmp -= 32; // 小写变大写
        else if(p1 == 3) tmp = '*'; // 变成星号
        // 处理 p2
        for(int i = 0; i < p2; i++)
        {
            t += tmp;
        }
    }
    // 处理 p3
    if(p3 == 2) reverse(t.begin(), t.end());
    ret += t;
}

int main()
{
    cin >> p1 >> p2 >> p3 >> s;
    n = s.size();
    for(int i = 0; i < n; i++)
    {
        char ch = s[i];
        if(s[i] != '-' || i == 0 || i == n - 1)//减号在开头或者末尾
            ret += ch;
        else
        {
            char left = s[i - 1], right = s[i + 1];
            // 判断是否展开
            if( (isdig(left) && isdig(right) && right > left) ||
                (islet(left) && islet(right) && right > left) )
            {
                // 展开
                add(left, right);
            }
            else
            {
                ret += ch;
            }
        }
    }
    cout << ret << endl;
    return 0;
}

2. 高精度

2.1 高精度加法

因为计算的时候是先进行计算个位 所以需要进行逆序 逆序的话就先从个位进行的加法

cpp 复制代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

// 高精度加法模板 - c = a + b;
void add(int c[], int a[], int b[])
{
    for(int i = 0; i < lc; i++)
    {
        c[i] += a[i] + b[i];        // 对应位相加
        c[i + 1] += c[i] / 10;      // 进位
        c[i] %= 10;                 // 本位留余数
    }
    if(c[lc]) lc++; // 最高位还有进位,长度+1
}

int main()
{
    string x, y;
    cin >> x >> y;
    // 1.拆分每一位,逆序存入数组(低位存在下标0)
    la = x.size();
    lb = y.size();
    lc = max(la, lb);

    for(int i = 0; i < la; i++)
        a[la - 1 - i] = x[i] - '0';
    for(int i = 0; i < lb; i++)
        b[lb - 1 - i] = y[i] - '0';

    // 2.模拟加法
    add(c, a, b);

    // 倒序输出
    for(int i = lc - 1; i >= 0; i--)
        cout << c[i];

    return 0;
}

2.2高精度减法

字符串比的是第一个数字 1<9

cpp 复制代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

// 比较大小:如果x < y 返回true
bool cmp(string& x, string& y)
{
    // 先比较长度
    if(x.size() != y.size())
        return x.size() < y.size();
    // 再按照字典序的方式比较
    return x < y;
}

// 高精度减法模板 - c = a - b,保证 a >= b
void sub(int c[], int a[], int b[])
{
    for(int i = 0; i < lc; i++)
    {
        c[i] += a[i] - b[i]; // 对应位相减,然后处理借位
        if(c[i] < 0)
        {
            c[i + 1] -= 1; // 借位
            c[i] += 10;
        }
    }
    // 处理前导零
    while(lc > 1 && c[lc - 1] == 0)
        lc--;
}

int main()
{
    string x, y;
    cin >> x >> y;
    // 比较大小,如果x<y,交换,输出负号 让大数减去小数
    if(cmp(x, y))
    {
        swap(x, y);
        cout << '-';
    }
    // 1. 拆分每一位,然后逆序放在数组中
    la = x.size();
    lb = y.size();
    lc = max(la, lb);
    for(int i = 0; i < la; i++)
        a[la - i - 1] = x[i] - '0';
    for(int i = 0; i < lb; i++)
        b[lb - i - 1] = y[i] - '0';

    // 2. 模拟减法的过程
    sub(c, a, b); // c = a - b

    // 输出结果
    for(int i = lc - 1; i >= 0; i--)
        cout << c[i];

    return 0;
}

swap是交换函数,作用:把两个变量的值互相交换。

2.3高精度乘法

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

// 高精度乘法模版:c = a * b
void mul(int c[], int a[], int b[])
{
    // 无进位相乘累加
    for(int i = 0; i < la; i++)
    {
        for(int j = 0; j < lb; j++)
        {
            c[i + j] += a[i] * b[j];
        }
    }
    // 统一处理进位
    for(int i = 0; i < lc; i++)
    {
        c[i + 1] += c[i] / 10;
        c[i] %= 10;
    }
    // 去除前导零
    while(lc > 1 && c[lc - 1] == 0)
        lc--;
}

int main()
{
    string x, y;
    cin >> x >> y;

    la = x.size();
    lb = y.size();
    lc = la + lb;

    // 逆序存入数组(低位存在下标0)
    for(int i = 0; i < la; i++)
        a[la - 1 - i] = x[i] - '0';
    for(int i = 0; i < lb; i++)
        b[lb - 1 - i] = y[i] - '0';

    mul(c, a, b);

    // 逆序输出
    for(int i = lc - 1; i >= 0; i--)
        cout << c[i];

    return 0;
}

2.4高精度除以低精度

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

const int N = 1e6 + 10;
typedef long long LL;
int a[N], b, c[N];
int la, lc;

LL div(int c[], int a[], int b)
{
    LL t = 0;
    for(int i = la - 1; i >= 0; i--) //除法是从最高位进行的
    {
        t = t * 10 + a[i];
        c[i] = t / b;
        t %= b;
    }
    while(lc > 1 && c[lc - 1] == 0)
        lc--;
    return t; // 返回余数
}

int main()
{
    string x;
    cin >> x >> b;
    la = x.size();
    for(int i = 0; i < la; i++)
        a[la - 1 - i] = x[i] - '0';
    lc = la;
    LL rem = div(c, a, b);
    // 输出商
    for(int i = lc - 1; i >= 0; i--)
        cout << c[i];
    cout << endl << "余数:" << rem << endl;
    return 0;
}

3.枚举

3.1 普通枚举

3.1.1 铺地毯

通过从后往前进行枚举 这样只要在范围内 那么这个肯定是最后的

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1e4 + 10;
int n;
int a[N], b[N], g[N], k[N];
int x, y;

int find()
{
    // 从后往前枚举地毯
    for(int i = n; i >= 1; i--)
    {
        // 判断点(x,y)是否在第i块地毯上
        // 地毯左下角(a[i],b[i]),长宽g[i],k[i]
        if(a[i] <= x && x <= a[i] + g[i] && b[i] <= y && y <= b[i] + k[i])
        {
            return i;
        }
    }
    return -1;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i] >> g[i] >> k[i];
    }
    cin >> x >> y;
    cout << find() << endl;
    return 0;
}

3.1.2 回文日期

在策略1里面需要遍历20000101到20240101 数据非常的庞大 这时候需要枚举240000遍

而策略2里面只需要遍历2000到2024 然后判断回文的月日合不合法,推广到完整 4 位年份范围 1000~9999,一共 8999个数

策略三进行遍历所有的月和日的组合 这样遍历12乘以31为372

cpp 复制代码
#include <iostream>
using namespace std;

int x, y;
// 闰年每月天数(2月29天)
int day[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
//当二月为u29天的时候 它的回文数必定是闰年92200229
int main()
{
    cin >> x >> y;
    int ret = 0;

    // 枚举月 i,日 j
    for (int i = 1; i <= 12; i++)
    {
        for (int j = 1; j <= day[i]; j++)
        {
            // 构造前面4位:日月翻转 日j月i → 月日颠倒作为年份
            int k = j % 10 * 1000 + j / 10 * 100 + i % 10 * 10 + i / 10;
            // 完整8位数:k(4位年份) + i(2位月) + j(2位日)
            int num = k * 10000 + i * 100 + j;
            
            if (x <= num && num <= y)
                ret++;
        }
    }
    cout << ret << endl;
    return 0;
}

3.1.3 扫雷

下标从1开始 防止下标越位

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1e4 + 10;
int n;
int a[N], b[N];

// a[1]不放地雷(a[1]=0)
int check1()
{
    a[1] = 0;
    for(int i = 2; i <= n + 1; i++)
    {
        a[i] = b[i - 1] - a[i - 1] - a[i - 2];
        if(a[i] < 0 || a[i] > 1) 
            return 0;
    }
    // 边界条件:a[n+1]必须等于0才合法
    if(a[n + 1] == 0) 
        return 1;
    else 
        return 0;
}

// a[1]放地雷(a[1]=1)
int check2()
{
    a[1] = 1;
    for(int i = 2; i <= n + 1; i++)
    {
        a[i] = b[i - 1] - a[i - 1] - a[i - 2];
        if(a[i] < 0 || a[i] > 1) 
            return 0;
    }
    if(a[n + 1] == 0) 
        return 1;
    else 
        return 0;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) 
        cin >> b[i];
    
    int ret = 0;
    ret += check1();
    ret += check2();
    cout << ret << endl;
    return 0;
}

3.2二进制枚举

3.2.1子集

cpp 复制代码
#include <vector>
using namespace std;

class Solution
{
public:
    vector<vector<int>> subsets(vector<int>& nums)
    {
        vector<vector<int>> ret;
        int n = nums.size();
        // st 代表状态,从 0 ~ (1<<n)-1
        for(int st = 0; st < (1 << n); st++) //向左移动
        {
            vector<int> tmp;
            for(int i = 0; i < n; i++)
            {
                // 判断 st 的第 i 位是否为 1
                if((st >> i) & 1)
                {
                    tmp.push_back(nums[i]);
                }
            }
            ret.push_back(tmp);
        }
        return ret;
    }
};

时间复杂度

3.2.2 费解的开关

进行一行的一行点亮 如果第一行没有点亮 那么第二行必须把第一行点亮 要不然后续就点不亮了

可以使用一个数进行存储 比如第一行10111 这个二进制的数为29 所以29对应亮灭亮亮亮

1^1=0 0^1=1 0^0=0 1^0=1

当前行为10010 的时候 下一行按的就是10010

当按位的时候是做高位需要按的时候(10000) 在左移(100000)的过程当中会把改变好的ai(01111)从5个位变成6个位(101111) 而最高位多了个1 这个时候就变成了6位 所以需要使用&(1<<5为32 然后为100000 100000-1为011111)所以就是101111&011111为001111 最高位多余的干扰 1 被直接清除,只留下有效低 5 位。

读取一行字符串输入:"00111"

cpp 复制代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 5;
int a[N];  // 原始灯状态,每行用一个整数二进制存储
int t[N];  // 临时副本,模拟操作

// 统计x二进制里面1的个数(统计按下开关数量)
int calc(int x)
{
    int cnt = 0;
    while(x)
    {
        cnt++;
        x &= x - 1; // 消除最低位的1
    }
    return cnt;
}

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        //多组数据要清空之前的数据
        memset(a, 0, sizeof a);
        // 读入5*5网格
        for(int i = 0; i < N; i++)
        {
            for(int j = 0; j < N; j++)
            {
                char ch;
                cin >> ch;
                // 存相反:'0'代表灯初始需要点亮(需要关掉),标记为1
                if(ch == '0')
                    a[i] |= 1 << j;
            //a[i] = a[i] | (1 << j);
            }
        }

        int ret = 0x3f3f3f3f;
        // 枚举【第一行所有按压方案】 st: 0 ~ 2^5 - 1
        for(int st = 0; st < (1 << N); st++)
        {
            memcpy(t, a, sizeof a);
            int push = st;    // 当前行按压状态
            int cnt = 0;      // 按压总次数

            for(int i = 0; i < N; i++)
            {
                cnt += calc(push);
                // 按压push,影响本行:自身、左、右
                t[i] ^= push ^ (push << 1) ^ (push >> 1); 

         因为^1会把0变成1 把1变成0 又因为需要把左边和右边的进行变化 
         所以需要进行换位置 就是<<左移和>>右移 把当前位置的左边和右边进行变化

                t[i] &= (1 << N) - 1; // 屏蔽超出5列的高位,防止越界影响

                // 当前行按压,会改变下一行同列灯
                t[i + 1] ^= push;

                // 关键:下一行必须按 t[i],才能把本行灯全部熄灭
                push = t[i];
            }
            // 全部操作完成后,最后一行必须全部熄灭(t[4]==0)才算合法方案
            if(t[N - 1] == 0)
                ret = min(ret, cnt);
        }

        // 题目规定超过6步输出-1
        if(ret > 6)
            cout << -1 << endl;
        else
            cout << ret << endl;
    }
    return 0;
}

时间复杂度:

3.2.3Even Parity

比如第一行为010的时候 为了满足第一行的上下左右为偶数 所以下一行为101 之后进行依次类推

010

101

因为题目里面指出来在这个数字的上下左右1的个数为偶数 所以0的个数也为偶数

根据一个异或的特性 在异或里面两个数一行的时候结果为0 x ^ x = 0 并且异或有交换律 所以在一个式子里面 1^0^1^0=0 而1=0^1^0

cpp 复制代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
int n;
int a[N]; // 用二进制存储状态
int t[N]; // 备份

// 判断 x->y 是否合法
// 返回 -1,表示不合法
// 其余的数,表示合法,并且表示 0->1 的次数
int calc(int x, int y)
{
    int sum = 0;
    for(int i = 0; i < n; i++)
    {
        if(((x >> i) & 1) == 0 && ((y >> i) & 1) == 1) sum++;
        if(((x >> i) & 1) == 1 && ((y >> i) & 1) == 0) return -1;
    }
    return sum;
}

int solve()
{
    int ret = 0x3f3f3f3f; // 记录最小的改变次数 初始化为无穷大,用来求最小值
    // 枚举第一行的最终状态
    for(int st = 0; st < (1 << n); st++)
    {
        memcpy(t, a, sizeof a);
        int change = st;
        int cnt = 0; // 统计 0->1 的次数
        bool flag = 1;
        for(int i = 1; i <= n; i++)
        {
            // 先判断 change 是否合法
            int c = calc(t[i], change);
            if(c == -1)
            {
                flag = 0;
                break;
            }
            cnt += c; // 累加次数
            // 当前行的最终状态
            t[i] = change;
            // 计算下一行的最终状态
            change = t[i - 1] ^ (t[i] << 1) ^ (t[i] >> 1);
            change &= (1 << n) - 1;
        }
        if(flag) ret = min(ret, cnt);
    }
    if(ret == 0x3f3f3f3f) return -1;
    else return ret;
}

int main()
{
    int T; cin >> T;
    for(int k = 1; k <= T; k++)
    {
        // 多组测试数据,记得清空
        memset(a, 0, sizeof a);
        cin >> n;
        for(int i = 1; i <= n; i++) // 避免越界访问 防止到[-1]
        { 
            for(int j = 0; j < n; j++)
            {
                int x; cin >> x;
                if(x) a[i] |= 1 << j;
            }
        }
        printf("Case %d: %d\n", k, solve());
    }
    return 0;
}

数组 a[i] 里面存的就是普通十进制整数;只是程序在运算的时候,把这个整数当成一串二进制比特来操作(移位、按位与、按位或)

时间复杂度

4.前缀和

4.1⼀维前缀和

cpp 复制代码
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, q;
LL a[N];
LL f[N]; // 前缀和数组
int main()
{
    cin >> n >> q;
    for(int i = 1; i <= n; i++) 
        cin >> a[i];
    // 处理前缀和数组
    for(int i = 1; i <= n; i++)
    {
        f[i] = f[i - 1] + a[i];
    }
    // 处理 q 次询问
    while(q--)
    {
        int l, r; 
        cin >> l >> r;
        cout << f[r] - f[l - 1] << endl;
    }
    return 0;
}

4.2最大子段和

cpp 复制代码
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int n;
LL f[N]; // 前缀和数组

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        LL x; cin >> x;
        f[i] = f[i - 1] + x;
    }
    LL ret = -1e20; //有的数是小于0的
    LL prevmin = 0; 
    for(int i = 1; i <= n; i++)
    {
        ret = max(ret, f[i] - prevmin); //获取最大的字段和
        prevmin = min(prevmin, f[i]); //获取前i数的最小和

      //如果数据是-1 -2 -3 代码进行颠倒后pre=-1 ret=0 就不对了 
      //通过找前i的最小值 让>i的数进行减去min(pre) 
      因为题目要求是非空的 所以pre的范围i要小于求的ret的i
      所以pre需要放在ret的后面 这样计算的时候pre里面的i永远小于ret的i
      //然后就获得了最新的子段

    cout << ret << endl;
    return 0;
}

premin得到的是前i个数的最小值

4.3二维前缀和

cpp 复制代码
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1010;
int n, m, q;
LL f[N][N];

int main()
{
    cin >> n >> m >> q;
    // 预处理二维前缀和矩阵
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            LL x;
            cin >> x;
            f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + x;
        }
    }
    // 处理 q 次查询
    while(q--)
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        cout << f[x2][y2] - f[x1 - 1][y2] - f[x2][y1 - 1] + f[x1 - 1][y1 - 1] << endl;
    }
    return 0;
}

4.4激光炸弹

这样的可以 这样的不可以

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 5010;
int n, m;
int a[N][N];
int f[N][N]; // 前缀和矩阵

int main()
{
    cin >> n >> m;
    while(n--)
    {
        int x, y, v; 
        cin >> x >> y >> v;
        x++, y++;          // 下标从 1 开始计数
        a[x][y] += v;      // 同一个位置有可能有多个目标
    }
    n = 5001;
    // 预处理前缀和矩阵
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + a[i][j];
        }
    }
    int ret = 0;
    m = min(m, n); // 如果 m 很大,相当于就是把整个区域全部摧毁
    // 枚举所有边长为 m 的正方形
    for(int x2 = m; x2 <= n; x2++)
    {
        for(int y2 = m; y2 <= n; y2++)
        {
            int x1 = x2 - m + 1, y1 = y2 - m + 1;
            ret = max(ret, f[x2][y2] - f[x1 - 1][y2] - f[x2][y1 - 1] + f[x1 - 1][y1 - 1]);
        }
    }
    cout << ret << endl;
    return 0;
}

【ACM 模式与核心代码模式】

相关推荐
oort1231 小时前
OortCodex 是奥尔特云 OortCloudSmart 推出的国产化工程化 AI 编码 Agent(奥尔特云编码智能体)
大数据·人工智能·算法
TheBestRucy1 小时前
基于Dify的旅游攻略&王者荣耀攻略智能助手项目
服务器·开发语言·人工智能·python·算法·旅游
你想知道什么?1 小时前
朴素贝叶斯算法-学习笔记
笔记·学习·算法
zlinear数据采集卡1 小时前
ZLinear产品线全景对比:D223 vs DABL7606 vs DABL-G511选型指南
arm开发·嵌入式硬件·算法·fpga开发·开源
数模竞赛Paid answer1 小时前
2026年华东杯数学建模C题收益率预测问题解题全过程文档及程序
算法·数学建模·数据分析·华东杯
一米阳光86611 小时前
软考(中级)软件设计师核心笔记(8)数据结构——树与二叉树
数据结构·笔记·职场发展·软考·软件设计师·中级职称
依然鸣2 小时前
PTA团体程序设计天梯赛L2真题讲解L2-005-008
开发语言·数据结构·c++·算法·深度优先·pat考试·图论
看浪的路人2 小时前
第2讲:一致性哈希——数据分片与负载均衡
算法·哈希算法
imaol12 小时前
数据结构--树
数据结构