记一下某居的笔试题

某居的线上笔试,凉凉了,发出来看看有没有大佬能指点指点解题思路的;

一、题目一

1.1 题目

有两长度为n(1 - 5000)的两个数组分别是 a, b (1 - 10000000),喜欢程度定义为 ai*bi的和
可以选择b的一个区间翻转一次,问最大喜欢程度
例:
3
10 1 1
1 10 1
输出:
102
解释:
翻转b数组的区间为[1,2],翻转后为10 1 1, a 数组为 10 1 1,对应位相乘求和后为 102;

1.2 暴力求解思路

思路:
枚举区间,对于每个区间,翻转数组中该区间内的元素,计算翻转后的喜欢程度并更新最大值,最后输出最大喜欢程度。时间复杂度为O(n^3)
代码:

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

using namespace std;

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

    vector<int> a(n);
    vector<int> b(n);
    for (int i = 0; i < n; i++) {
    
    
        cin >> a[i];
    }
    for (int i = 0; i < n; i++) {
    
    
        cin >> b[i];
    }

    // 计算原始的喜欢程度
    long long ans = 0;
    for (int i = 0; i < n; i++) {
    
    
        ans += (long long)a[i] * b[i];
    }

    // 枚举区间,计算翻转区间后的喜欢程度
    for (int i = 0; i < n; i++) {
    
    
        for (int j = i; j < n; j++) {
    
    
            // 翻转区间
            reverse(a.begin() + i, a.begin() + j + 1);

            // 计算喜欢程度
            long long cur = 0;
            for (int k = 0; k < n; k++) {
    
    
                cur += (long long)a[k] * b[k];
            }

            // 更新最大值
            ans = max(ans, cur);

            // 恢复原数组
            reverse(a.begin() + i, a.begin() + j + 1);
        }
    }

    cout << ans << endl;

    return 0;
}

1.3 ai生成的优化思路

思路:
我可以为你提供一种解决方案,基于贪心算法和前缀和优化,时间复杂度为O(n)。

首先,我们可以将乘积 aibi 换算成 (a+b)(a-b),其中 a = (ai+bi)/2,b = (ai-bi)/2。我们只需要考虑最大的 (a+b) 和 (a-b) 就可以了。

以 (a+b) 为例,我们可以先计算出所有相邻两个元素之差 diff[i] = (a[i+1]+b[i+1]) - (a[i]+b[i]),然后就可以根据差值的正负来判断是否需要翻转区间的位置。具体来说,我们定义 sum1 = a[1]+b[1],sum2 = a[1]-b[1],然后从位置 i=2 开始循环遍历:

  • 如果 diff[i] > 0,那么说明 a[i]+b[i] 比 a[i-1]+b[i-1] 大,也就是说后面的候选区间有更好的选择,我们记录 maxSum = max(maxSum, sum1+sum2) 并标记需要翻转区间 [start, i],同时更新 sum1 和 sum2。
  • 如果 diff[i] <= 0,那么说明可能需要将当前位置 i-1 加入到候选区间中,我们只需要更新 sum1 和 sum2 即可。

对于 maxSum 的更新,如果当前需要翻转的区间是 [start, end],那么我们可以将 a[start],b[start] 和 a[end],b[end] 翻转一下,也就是交换 a[start] 和 a[end],交换 b[start] 和 b[end],从而得到新的 (maxSum, maxA, maxB)。注意,我们只需要在需要翻转的区间中选择 a+b 最大的一段,而不需要选择需要翻转的最大区间。因为有可能一些区间之间的差值非常小,而会翻转很多次,导致时间复杂度变高。

最后,我们需要再次计算一遍翻转区间 [start, end] 的存在性,并将 a 和 b 分别进行前缀和优化,以便快速计算区间和。具体来说,我们分别计算 a 和 b 的前缀和 sa 和 sb,然后最大喜欢程度 n 就等于区间 [1, start-1] 的 a 和 sa,加上区间 [start, end] 的 a 和 sa 的差值的绝对值,再加上区间 [end+1, n] 的 a 和 sa,再乘以 b 和 sb 类似的计算即可得到答案。

代码实现如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
int n, a[MAXN], b[MAXN];
long long sa[MAXN], sb[MAXN];
int diff[MAXN], pos[MAXN];
long long maxSum, maxA, maxB;
void solve()
{
    
    
    sa[0] = sb[0] = pos[0] = 0;
    for (int i = 1; i <= n; ++i) {
    
    
        cin >> a[i] >> b[i];
        sa[i] = sa[i - 1] + a[i];
        sb[i] = sb[i - 1] + b[i];
        pos[i] = i - 1;
        diff[i] = a[i] + b[i] - a[i-1] - b[i-1];
    }
    maxSum = sa[1] + sb[1] + sa[n] - sa[1] + sb[n] - sb[1];
    maxA = sa[1] - sb[1];
    maxB = sb[1] - sa[1];
    int start = 1, end = 0;
    long long sum1 = sa[1] + sb[1], sum2 = sa[1] - sb[1];
    for (int i = 2; i <= n; ++i) {
    
    
        if (diff[i] > 0) {
    
    
            long long tmpSum1 = sum1 + sa[i] - sa[start] + sb[n] - sb[i-1];
            long long tmpSum2 = sum2 + sa[i] - sa[start] - sb[n] + sb[i-1];
            if (tmpSum1 + tmpSum2 > maxSum) {
    
    
                maxSum = tmpSum1 + tmpSum2;
                maxA = tmpSum1 / 2;
                maxB = tmpSum2 / 2;
                if (i <= n - 1 && diff[i+1] <= 0 && sa[i+1]-sa[start] > sb[n]-sb[i]) {
    
    
                    ++i;
                    tmpSum1 += sa[i] - sa[start] - sb[n] + sb[i-1];
                    tmpSum2 += sb[n] - sb[i-1] - sa[i] + sa[start];
                }
                start = pos[i] + 2;
                end = i;
            }
            sum1 += sa[i] - sa[start] + sb[i] - sb[start-1];
            sum2 += sa[i] - sa[start] - sb[i] + sb[start-1];
        } else {
    
    
            sum1 += sa[i] - sa[i-1];
            sum2 += sb[i] - sb[i-1];
            pos[i] = pos[i-1];
        }
    }
    sum1 = sa[n] - sa[start-1];
    sum2 = sb[n] - sb[start-1];
    for (int i = n; i >= end+1; --i) {
    
    
        diff[i] = a[i] + b[i] - a[i-1] - b[i-1];
        if (diff[i] <= 0) {
    
    
            sum1 -= sa[i] - sa[end+1];
            sum2 -= sb[i] - sb[end+1];
        }
    }
    long long answer = abs(sum1*maxB) + abs(sum2*maxA);
    cout << answer << endl;
}
int main()
{
    
    
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    solve();
    return 0;
}

代码中,我们使用 sa 和 sb 存储 a 和 b 的前缀和,diff 存储相邻两个元素之差,pos 存储需要翻转区间的左端点。对于 maxSum 的更新,我们还需要考虑当前选择的区间是否可以与后面相邻的区间合并,从而得到更好的选择。

最后,我们可以将 a 和 b 分别进行前缀和优化,以便快速计算区间和。在计算喜欢程度时,我们还需要注意取绝对值。

综上,这种贪心算法的时间复杂度为 O(n),相比于暴力方法和动态规划方法更为高效,能够解决数据规模较大的问题。

mmd,优化思路的代码似乎不太对,麻了

二、模拟加密解密题

题目:
对于一个字符串:kingsoft,爷爷喜欢把它加密,加密方式为先取头,再取尾,直到取完,
按照爷爷加密的方法加密完后是这样: ktifnogs,现在爷爷把它传输给你了,你需要进行解密并输出字符串。
这个题我是没想到啥优化的,就直接逆着加密方式来取就好了,需要注意的就是奇数个字符和偶数个字符取得问题,别的没啥;

三、动态规划题

忘了

猜你喜欢

转载自blog.csdn.net/BinBinCome/article/details/131025687