机试算法刷题记录


1.[类动规划分]截断数组

给定一个长度为 $n$ 的数组 $a_1,a_2,…,a_n$。现在,要将该数组从中间截断,得到三个非空子数组。

要求,三个子数组内各元素之和都相等。请问,共有多少种不同的截断方法?

  • 因为要把数组均分三份,总和一定得是3的倍数

  • 遍历前缀和数组,边扫描边记录哪些地方可以切第一刀,哪些地方可以切第二刀。如果位置j可以切第二刀,那么它与前面第一刀进行组合

image-20230401004426476

根据第二刀的位置划分出 $n$ 种情况。

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

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
int s[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        s[i] = s[i - 1] + x;
    }

    if (s[n] % 3) puts("0");
    else
    {
        LL res = 0, cnt = 0;
        for (int j = 2; j < n; j ++ )
        {
            if (s[j - 1] == s[n] / 3) cnt ++ ;
            if (s[j] == s[n] / 3 * 2) res += cnt;
        }

        printf("%lld\n", res);
    }

    return 0;
}

2.[差分]改变数组元素

image-20230401222342795

image-20230401222308017

a 数组表示 v 数组各个元素的操作次数。当 a 数组元素操作次数 $\geq 1$ 时, v 数组元素的值为 $1$。

可以运用差分算法进行操作。

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

using namespace std;

const int N = 200010;

int n;
int b[N];

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        scanf("%d", &n);
        memset(b, 0, (n + 1) * 4);		// int为4个字节
        for (int i = 1; i <= n; i ++ )
        {
            int x;
            scanf("%d", &x);
            x = min(x, i);
            int l = i - x + 1, r = i;
            b[l] ++, b[r + 1] -- ;
        }
        for (int i = 1; i <= n; i ++ ) b[i] += b[i - 1];
        for (int i = 1; i <= n; i ++ ) printf("%d ", !!b[i]);	// !!表示int转bool
        puts("");
    }

    return 0;
}

3.[二分+哈希]我在哪?

image-20230402111351029

找到最小的 $K$,使得任意两个长度为 $K$ 的子串都不相同。

能否二分—->是否具有二段性

image-20230402112849311

二分 + STL Set(哈希表) $O(n^2logn)$

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

using namespace std;

int n;
string str;
unordered_set<string> S;

bool check(int mid)
{
    S.clear();
    for (int i = 0; i + mid - 1 < n; i ++ )
    {
        string s = str.substr(i, mid);
        if (S.count(s)) return false;
        S.insert(s);
    }

    return true;
}

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

    int l = 1, r = n;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }

    cout << r << endl;

    return 0;
}

4.[二分]垦田计划

image-20230402124023627

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
 
constexpr int N = 1e5 + 10;
int n, m, k, mx = -1;
pair<int, int> a[N];
 
int check(int x) {
    int cos = 0;
    for (int i = 1; i <= n; i++) {
        if (a[i].first >= x) 
            cos += (a[i].first - x) * a[i].second;
        else
            break;
    }
    return cos <= m;
}
 
int main()
{  
    cin >> n >> m >> k;
 
    for (int i = 1; i <= n; i++) {
        cin >> a[i].first >> a[i].second;
        mx = max(mx, a[i].first);
    }
    
    sort(a + 1, a + 1 + n, [&](const pair<int, int> &x, const pair<int, int> &y) {
        return x.first > y.first;
    });
 
    int l = k, r = mx;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
 
    cout << l << "\n";
    
    return 0;
}

5.[二分/哈希]四平方和

四平方和定理,又称为拉格朗日定理:

每个正整数都可以表示为至多 $4$ 个正整数的平方和。

如果把 $0$ 包括进去,就正好可以表示为 $4$ 个数的平方和。

比如:

$5=0^2 +0^2+1^2+2^2$
$7=1^2+1^2+1^2+2^2$

对于一个给定的正整数,可能存在多种平方和的表示法。

要求你对 $4$ 个数排序:

$0≤a≤b≤c≤d$

并对所有的可能表示法按 $a,b,c,d$ 为联合主键升序排列,最后输出第一个表示法。

image-20230402174601790

如果找到字典序最小的解—->存到结构体中并排序,找到 $t=n-a^2-b^2$字典序最小的组合

查询$c^2+d^2$的值的结果是否存在”

二分 $O(N^2logN)$

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

using namespace std;

const int N = 2500010;

struct Sum
{
    int s, c, d;
    bool operator< (const Sum &t)const
    {
        if (s != t.s) return s < t.s;
        if (c != t.c) return c < t.c;
        return d < t.d;
    }
}sum[N];

int n, m;

int main()
{
    cin >> n;

    for (int c = 0; c * c <= n; c ++ )
        for (int d = c; c * c + d * d <= n; d ++ )
            sum[m ++ ] = {c * c + d * d, c, d};

    sort(sum, sum + m);

    for (int a = 0; a * a <= n; a ++ )
        for (int b = 0; a * a + b * b <= n; b ++ )
        {
            int t = n - a * a - b * b;
            int l = 0, r = m - 1;
            while (l < r)
            {
                int mid = l + r >> 1;
                if (sum[mid].s >= t) r = mid;
                else l = mid + 1;
            }
            if (sum[l].s == t)
            {
                printf("%d %d %d %d\n", a, b, sum[l].c, sum[l].d);
                return 0;
            }
        }

    return 0;
}

哈希表 $O(N^2)$(仅理论,哈希表 STL 常数大,时间复杂度高)

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

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 2500010;

int n, m;
unordered_map<int, PII> S;

int main()
{
    cin >> n;

    for (int c = 0; c * c <= n; c ++ )
        for (int d = c; c * c + d * d <= n; d ++ )
        {
            int t = c * c + d * d;
            if (S.count(t) == 0) S[t] = {c, d};		// 字典序最小
        }

    for (int a = 0; a * a <= n; a ++ )
        for (int b = 0; a * a + b * b <= n; b ++ )
        {
            int t = n - a * a - b * b;
            if (S.count(t))
            {
                printf("%d %d %d %d\n", a, b, S[t].x, S[t].y);
                return 0;
            }
        }

    return 0;
}

6.[二分]分巧克力

image-20230402213123319

需满足不等式关系:$\sum_{i=0}^{n-1}\left\lfloor\frac{h_{1}}{\text { mid }}\right\rfloor \cdot\left\lfloor\frac{w_{j}}{\text { mid }}\right\rfloor<k$

image-20230402213110230

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

using namespace std;

const int N = 100010;

int n, k;
int h[N], w[N];

bool check(int mid)
{
    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        res += (h[i] / mid) * (w[i] / mid);
        if (res >= k) return true;
    }

    return false;
}

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

    int l = 1, r = 1e5;
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }

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

    return 0;
}

7.[DP]接龙数列

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

using namespace std;

const int N = 100010;

int n;
int g[10];

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

    int res = 0;
    char num[20];
    for (int i = 0; i < n; i ++ )
    {
        scanf("%s", num);
        int l = num[0] - '0', r = num[strlen(num) - 1] - '0';
        int f = max(1, g[l] + 1);
        g[r] = max(g[r], f);
        res = max(res, f);
    }

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

8.[滑动窗口]子矩阵

考察单调队列

image-20230425191554955

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

using namespace std;

typedef long long LL;
const int N = 1010, MOD = 998244353;

int n, m, A, B;
int w[N][N];
int rmax[N][N], rmin[N][N];
int q[N];

void get_max(int a[], int b[], int tot, int k)
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )
    {
        if (hh <= tt && q[hh] <= i - k) hh ++ ;			// 判断队头是否滑出窗口
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
        q[ ++ tt] = i;
        b[i] = a[q[hh]];		// 当前滑动窗口的最大值
    }
}

void get_min(int a[], int b[], int tot, int k)
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )
    {
        if (hh <= tt && q[hh] <= i - k) hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[ ++ tt] = i;
        b[i] = a[q[hh]];
    }
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &A, &B);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            scanf("%d", &w[i][j]);

    for (int i = 0; i < n; i ++ )
    {
        get_max(w[i], rmax[i], m, B);		// 对每行的每个滑动窗口进行单调队列计算
        get_min(w[i], rmin[i], m, B);
    }

    int res = 0;

    int a[N], b[N], c[N];
    
    // 求长度为 A 的窗口的最值 
    for (int i = B - 1; i < m; i ++ )
    {
        for (int j = 0; j < n; j ++ ) a[j] = rmax[j][i];	// 复制出每行最大值
        get_max(a, b, n, A);
        for (int j = 0; j < n; j ++ ) a[j] = rmin[j][i];	// 复制出每行最小值
        get_min(a, c, n, A);
        for (int j = A - 1; j < n; j ++ )
            res = (res + (LL)b[j] * c[j]) % MOD;
    }

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

9.[高精度]10进制 VS 2进制

秦九韶算法

image-20230426164654632

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

using namespace std;

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

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

    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

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

void print(vector<int>& A)
{
    for (int i = A.size() - 1; i >= 0; i -- )
        cout << A[i];
    cout << endl;
}

int main()
{
    string a;
    cin >> a;

    vector<int> A;
    for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');

    vector<int> B;
    while (A.size() > 1 || A[0] > 0)
    {
        int r;
        A = div(A, 2, r);		// 每次除以2, 并记录余数
        B.push_back(r);			// 存的即为逆序
    }

    vector<int> C;
    for (int x: B)
    {
        C = mul(C, 2);
        C = add(C, x);
    }

    print(C);

    return 0;
}

10.[日期问题]日期差值

计算天的差值,只需分别计算其到公元1年1月1日的差即可。

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

using namespace std;

const int months[] = {
    0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

int is_leap(int year)
{
    if (year % 100 && year % 4 == 0 || year % 400 == 0)
        return 1;
    return 0;
}

int get_month_days(int year, int month)
{
    int res = months[month];
    if (month == 2) res += is_leap(year);
    return res;
}

int get_total_days(int y, int m, int d)
{
    int res = 0;
    for (int i = 1; i < y; i ++ )
        res += 365 + is_leap(i);

    for (int i = 1; i < m; i ++ )
        res += get_month_days(y, i);

    return res + d;
}

int main()
{
    int y1, m1, d1, y2, m2, d2;
    while (scanf("%04d%02d%02d", &y1, &m1, &d1) != -1)
    {
        scanf("%04d%02d%02d", &y2, &m2, &d2);
        printf("%d\n", abs(get_total_days(y1, m1, d1) - get_total_days(y2, m2, d2)) + 1);
    }

    return 0;
}

11.区间合并

class Solution {
    public int[][] merge(int[][] intervals) {
        /*
        贪心策略:
        1.按照左边界升序排序
        2.当intervals[i][0]<=intervals[i-1][1]时,说明能合并,更新intervals[i][1]=max(intervals[i][1],intervals[i-1][1])
        3.当intervals[i][0]>intervals[i-1][1]时,区间不重叠,将前面区间加入结果,更新新的左边界
        4.理论上最后一个结果不会加人,手动加入即可
         */
        List<int[]> list = new ArrayList<>();
        Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
        // 每个重叠区间组左边界
        var len = intervals.length;
        int start = intervals[0][0];
        for (int i = 1; i < len; i++) {
            if (intervals[i][0] > intervals[i - 1][1]) {
                list.add(new int[]{start, intervals[i - 1][1]});
                // 更新区间组起点
                start = intervals[i][0];
            } else {
                // 更新区间组最大值,表示该区间组合并后的右边界
                intervals[i][1] = Math.max(intervals[i][1], intervals[i - 1][1]);
            }
        }
        // 添加最后一个区间(这使得区间数目为1也成立)
        list.add(new int[]{start, intervals[len - 1][1]});
        return list.toArray(new int[0][]);
    }
}

文章作者: ShiQuLiZhi
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ShiQuLiZhi !
评论
  目录