1960 字
10 分钟
美团技术笔试第二场

好久没写题了,就 A 了两题 。。

第一题#

题意描述#

有 TT 组测试用例,每次测试用例给你一个 nn,让你求一个数 mm,使得最大公因数 gcd(n,m)gcd(n,m) 为素数

数据范围#

大概是 T≤100T \le 100 , 2≤n≤1000002 \le n \le 100000

解法#

线性筛 + 枚举#

时间复杂度 O(n+Tnlnn)O(n + T\frac{n}{ln n})

首先用线性筛求出所有的素数,然后枚举所有素数,看是否是 nn 的约数

代码如下

#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int p[N], cnt = 0;
bool st[N];
void init(int n) {
for (int i = 2; i <= n; i ++ ) {
if (!st[i]) p[cnt ++ ] = i;
for (int j = 0; i <= n / p[j]; j ++ ) {
st[i * p[j]] = true;
if (i % p[j] == 0) break;
}
}
}
void solve() {
int n; scanf("%d", &n);
// cout << cnt << endl;
for (int i = 0; p[i] <= n; i ++ )
if (n % p[i] == 0) {
printf("%d\n", p[i]);
break;
}
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
init(N - 1);
int T; scanf("%d", &T);
while (T -- ) {
solve();
}
}
TIP

hh,事实上线性筛本质上就是用一个合数的最小素数因数将该合数筛掉,因此只需要保存每个数的最小素数因子即可。所以最后的枚举多此一举,时间复杂度应该为 O(n+T)O(n+T)

第二题#

题意描述#

给你一个长度为 nn 的数组,然后你可以执行无限次以下操作

选择数组下标不同的两个数 ai,aja_i,a_j,使得数组中 ai→ai−1a_i \rightarrow a_i - 1,aj→aj+1a_j \rightarrow a_j+1

求使得使得这个数组极差最小的最少操作数

NOTE

极差:数组中最大值和最小值之差

数据范围#

  • n≤100000n \le 100000
  • 1≤ai≤1091 \le a_i \le 10^9

解法#

思维#

时间复杂度 O(nlogn)O(nlogn)

可以想象这个数组极差最小是多少?一定是 00 或者 11

假设此时极差不为 00 或者 11,也就是最大值 amaxa_{max} 和最小值 amina_{min} 之差 ≥2\ge 2 ,那么此时可以通过一次操作将这两个数的差降到 00 或者 11。如果进行了此次调整,极差仍然不为 00 或者 11,继续执行改步骤即可

另外,假设 S=∑aiS = \sum a_i ,由于每次操作都有一个数减一,一个数加一,因此每次操作 SS 不变。

因此,如果 S%n=0S \% n = 0,说明最后极差一定为 00,且每个数都为 S/nS / n;如果 S%n≠0S \% n \neq 0,说明最后极差一定为 11,且有 S%nS \% n 个数为 Sn+1\frac{S}{n} + 1 ,n−S%nn - S \% n 个数为 S/nS / n

根据贪心性质,将数组从大到小排序,前面 S%nS \% n 个数变成 Sn+1\frac{S}{n} + 1 和后面的n−S%nn - S \% n 个数变成 S/nS / n 需要操作数为 resres

最终答案就是 res/2res / 2

代码如下

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int q[N];
int n;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
scanf("%d", &n);
LL sum = 0;
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]), sum += q[i];
sort(q, q + n);
reverse(q, q + n);
LL t1 = sum / n, t2 = sum % n;
LL res = 0;
for (int i = 0; i < n; i ++ )
if (i < t2) res += abs(q[i] - t1 - 1);
else res += abs(q[i] - t1);
printf("%lld\n", res / 2);
}
CAUTION

注意开 long long


WARNING

下题暂未给出代码,仅给出目前思路,等 A 了之后再上传

第三题#

题目大意#

给你一个长度为 nn 的数组,其数组之和为∑ai\sum a_i,然后接下来有两个人顺序执行两个操作

  1. 小红先选择一段区间 [l,r][l,r],对该区间的每个数都乘 kk,小红希望选择的区间能够让最后的 sumsum 尽可能大
  2. 小明再选择一段区间 [l,r][l,r],对该区间的每个数都乘 kk,小明希望选择的区间能够让最后的 sumsum 尽可能小

问:在经过这两个操作之后,sumsum 的值为多少?

数据范围#

  • 1≤n≤10001\le n \le 1000
  • −105≤k≤105-10^5 \le k \le 10^5
  • −105≤ai≤105-10^5 \le a_i \le 10^5

解法#

问题一   假设最开始的数组累和为 sumsum,修改的区间是 [l,r][l,r],将其每个值都乘 kk,最后的 sumcsum_c 是多少?

答:sumc=sum+(k−1)∗∑i=lraisum_c = sum + (k - 1) * \sum_{i=l}^r a_i

问题二   sumsum 值难道不是取最大值或者最小值吗?

答:这里显然不是,sumsum 是在题目限定下一个确切的值。

假设小红已经做出选择,根据问题一,小明只需要找一个最大或者最小的连续字段和便可。因此,小红要想最后的 sumsum 取最大值,是所有区间里面,最后的 sumsum 最大的区间

暴力#

时间复杂度 O(n3)O(n^3)

根据上述分析,有一个很暴力的解法就是枚举小红选择的区间,然后小明对每个修改后的数组求一遍最大或者最小子段和,然后所有区间能求得的最大值就是 sumsum

线段树优化#

---Update: 2024-08-28

IMPORTANT

hh,这题事实上线段树就解决了,下面的分析完全不需要。 暴力枚举小红的区间,然后每个区间乘 kk,线段树可以 lognlogn 更新;然后求最大/小连续子区间也可以线段树求。

数据结构太弱了 ~

时间复杂度 O(n2logn)O(n^2 logn)

首先先把特殊情况去掉,如果 k=1k = 1 ,则无论怎么样,sumsum 的值都不会变,因此情况分为 k>1k > 1 和 k<1k < 1;以下分析只考虑 k>1k > 1 的情况, k<1k < 1 的分析类似。

上面这个暴力的时间复杂度问题在于枚举了小红选择的所有区间,并且小红和小明最后所选的区间也没有任何联系,那么能否优化这部分?

答案是肯定的。我们仔细想一想,小红和小明选择的区间是否存在交叉的情况

TIP

区间A: [1,5],区间B:[2,6]。区间 A B 表示交叉,如果 A 完全包含 B 或者 B 完全包含 A,不表示交叉;同时,A 和 B 没有交集也表示不交叉。

假设最后选择中,小红和小明选择的区间是由交叉部分的,如果交叉部分的和的值是等于 0 的,无论是小红还是小明,加上这个区间都没有意义,相当于两人的选择是没有交集的;如果交叉部分的和的值是大于 0 的,那么对于小明来说,不选择这个区间比选择这个区间更优,因此可以去掉这个区间;如果交叉部分的和的值是小于 0 的,那么对于小红来说,这部分区间反而给小明利用了,因此不选择这部分区间比选择这部分区间更优

综上,小红小明最终选择的两区间,一定是不交叉的。(注意可以是包含关系)

因此这里分为三种情况考虑:

假设同样枚举小红区间为 [l,r][l,r]

  1. 小红与小明区间没有交集

    小明则无非是选择 [1,l−1][1,l-1] 或者 [r+1,n][r + 1, n] 的最小子段和

  2. 小明区间完全包含小红区间

    小明无非就是选择以 [1,l−1][1,l-1] 中以 l−1l-1 为终点的最小子段和的区间以及 [r+1,n][r + 1, n] 中以 r + 1 为起点的最小子段和的区间以及 [l,r][l,r]

  3. 小红区间完全包含小明区间

    [l,r][l,r] 区间的最小字段和

可以看到上述 1、2 好求,但是 3 目前只能想到用线段树来维护,因此每次求这个区间最小字段和的时间复杂度为 O(logn)O(logn)

写在最后,第三题有更好的解法欢迎讨论

吐槽#

我太菜了,果然只有对算法融会贯通才能写出第三题嘛 ┭┮﹏┭┮