某居的线上笔试,凉凉了,发出来看看有没有大佬能指点指点解题思路的;
有两长度为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;
思路:
枚举区间,对于每个区间,翻转数组中该区间内的元素,计算翻转后的喜欢程度并更新最大值,最后输出最大喜欢程度。时间复杂度为O(n^3)
代码:
#include
#include
#include
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;
}
思路:
我可以为你提供一种解决方案,基于贪心算法和前缀和优化,时间复杂度为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 开始循环遍历:
对于 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
#include
#include
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),相比于暴力方法和动态规划方法更为高效,能够解决数据规模较大的问题。
题目:
对于一个字符串:kingsoft
,爷爷喜欢把它加密,加密方式为先取头,再取尾,直到取完,
按照爷爷加密的方法加密完后是这样: ktifnogs
,现在爷爷把它传输给你了,你需要进行解密并输出字符串。
这个题我是没想到啥优化的,就直接逆着加密方式来取就好了,需要注意的就是奇数个字符和偶数个字符取得问题,别的没啥;
忘了