小美的平衡矩阵
题目描述
小美拿到了一个 n × n
的矩阵,其中每个元素是 0
或者 1
。小美认为一个矩形区域是完美的,当且仅当该区域内 0
的数量恰好等于 1
的数量。现在,小美希望你回答有多少个 i × i
的完美矩形区域。你需要回答 1 ≤ i ≤ n
的所有答案。
输入描述
第一行输入一个正整数 n
,代表矩阵大小。
接下来的 n
行,每行输入一个长度为 n
的 01
串,用来表示矩阵,1≤ n ≤ 200
。
输出描述
输出 n
行,第 i
行输出 i × i
的完美矩形区域的数量。
解题思路
通过二维前缀和可快速得到一个矩形区域内的 0
或 1
的个数。
令 dp[i][j]
= 以 (1,1)
为左上角,(i,j)
为右下角的矩形中 1
的个数,则有如下递推式:
dp[i][j] = dp[i-1][j] + dp[i][j-1] -dp[i-1][j-1]
对于一个以 (i,j)
为左上角、长为 k
的矩形,其中 1
的个数为:
dp[i + k][j + k] - dp[i + k][j - 1] - dp[i - 1][j + k] + dp[i - 1][j - 1]
代码实现
const int N = 2e2 + 5;
int dp[N][N];
char s[N];
int main() {
int n;
scanf("%d", &n);
// 输入、计算二维前缀和
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= n; j++)
dp[i][j] = dp[i][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1] + (s[j] == '1');
}
// mp[i] = 大小为 i*i 的矩形的数量
unordered_map<int, int> mp;
// 枚举每个大小为 i*i 的矩形
for (int i = 1, t; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 1; i + k <= n && j + k <= n; k += 2) {
t = dp[i + k][j + k] - dp[i + k][j - 1] - dp[i - 1][j + k] + dp[i - 1][j - 1];
if (t * 2 == (k + 1) * (k + 1))mp[k + 1]++;
}
}
}
// 输出每个答案
for (int i = 1; i <= n; i++)
printf("%d\n", mp[i]);
return 0;
}
时间复杂度:O(n^3)
。
空间复杂度:O(n^2)
。
小美的数组询问
题目描述
小美拿到了一个由正整数组成的数组,但其中有一些元素是未知的(用 0
来表示)。现在小美想知道,如果那些未知的元素在区间 [l,r]
范围内随机取值的话,数组所有元素之和的最小值和最大值分别是多少?共有 q
次询问。
输入描述
第一行输入两个正整数 n,q
,代表数组大小和询问次数。
第二行输入 n
个整数 a_i
,其中如果输入的 a_i
为 0
,那么说明 a_i
是未知的。
接下来的 q
行,每行输入两个正整数 l,r
,代表一次询问。
1≤ n,q ≤ 10^5
0 ≤ a_i ≤ 10^9
1≤ l ≤ r ≤ 10^9
输出描述
输出 q
行,每行输出两个正整数,代表所有元素之和的最小值和最大值。
解题思路
选 l
得到最小值,选 r
得到最大值。
代码实现
typedef long long ll;
int main() {
int n, q, cnt = 0;
ll x, l, r, sum = 0;
scanf("%d%d", &n, &q);
while (n--) {
scanf("%lld", &x);
x == 0 ? cnt++ : sum += x;
}
while (q--) {
scanf("%lld%lld", &l, &r);
printf("%lld %lld\n", sum + l * cnt, sum + r * cnt);
}
return 0;
}
时间复杂度:O(n)
。
空间复杂度:O(1)
。
小美的 MT
题目描述
MT
是美团的缩写,因此小美很喜欢这两个字母。现在小美拿到了一个仅由大写字母组成字符串,她可以最多操作 k
次,每次可以修改任意一个字符。小美想知道,操作结束后最多共有多少个 M
和 T
字符?
输入描述
第一行输入两个正整数 n,k
,代表字符串长度和操作次数。
第二行输入一个长度为 n
的、仅由大写字母组成的字符串。
1≤ k ≤ n ≤ 10^5
输出描述
输出操作结束后最多共有多少个 M
和 T
字符。
解题思路
尽可能修改每个不是 M
或 T
的字符。
代码实现
int main() {
int n, k, cnt = 0;
char c;
scanf("%d%d\n", &n, &k);
while (n--) {
scanf("%c", &c);
if (c == 'M' || c == 'T')cnt++;
else if (k > 0)cnt++, k--;
}
printf("%d", cnt);
return 0;
}
时间复杂度:O(n)
。
空间复杂度:O(1)
。
小美的朋友关系
题目描述
小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友关系就淡忘了。
现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识?
事件共有 2
种:
1 u v
:代表编号u
的人和编号v
的人淡忘了他们的朋友关系。2 u v
:代表小美查询编号u
的人和编号v
的人是否能通过朋友介绍互相认识。
输入描述
第一行输入三个正整数 n,m,q
,代表总人数,初始的朋友关系数量,发生的事件数量。
接下来的 m
行,每行输入两个正整数 u,v
,代表初始编号 u
的人和编号 v
的人是朋友关系。
接下来的 q
行,每行输入三个正整数 op,u,v
,含义如题目描述所述。
1≤ n ≤ 10^9
1≤ m,q ≤ 10^5
1≤ u,v ≤ n
1≤ op ≤ 2
保证至少存在一次查询操作。
输出描述
对于每次 2
号操作,输出一行字符串代表查询的答案。如果编号 u
的人和编号 v
的人能通过朋友介绍互相认识,则输出"Yes"。否则输出"No"。
解题思路
n
的规模超过10^6
,需要离散化。- 通过并查集可快速判断两人是否为朋友关系。
- 并查集不方便删除,故采用反向并查集。对于每个淡忘操作
(u,v)
,若初始存在 关系(u,v)
,则此操作前均存在关系(u,v)
,则此操作后均不存在关系(u,v)
。因此,反向顺序进行操作,将删除转为添加,将结果正向输出即为答案。
代码实现
const int N = 1e5 + 5;
// 并查集
int p[N];
// 初始关系、关系记录
set<pair<int, int>> irs, rs;
// 操作
int ops[N][3];
// 离散化
int idx = 0;
unordered_map<int, int> mp;
/**
* 将编号x转换为离散化后的值
*/
int dsz(int x) {
if (mp[x] == 0)mp[x] = ++idx;
return mp[x];
}
/**
* 找到节点x所在集合的根节点
*/
int find(int x) {
if (p[x] != x)p[x] = find(p[x]);
return p[x];
}
/**
* 合并节点u和节点v所在两个集合
*/
void unite(int u, int v) {
int pu = find(u), pv = find(v);
if (pu != pv)p[pu] = pv;
}
int main() {
int n, m, q, op, u, v;
scanf("%d%d%d", &n, &m, &q);
iota(p, p + N, 0);
// 关系记录 = 初始关系
while (m--) {
scanf("%d%d", &u, &v);
u = dsz(u), v = dsz(v);
if (u > v)swap(u, v);
irs.insert({u, v});
rs.insert({u, v});
}
// 记录操作、关系记录 = 初始关系 - 淡忘关系
for (int i = 0; i < q; i++) {
scanf("%d%d%d", &op, &u, &v);
u = dsz(u), v = dsz(v);
if (u > v)swap(u, v);
ops[i][0] = op, ops[i][1] = u, ops[i][2] = v;
if (op == 1)rs.erase({u, v});
}
// 根据关系记录建立并查集
for (auto &[a, b]: rs) unite(a, b);
// 从前往后是删除关系,从后往前是添加关系
bool r[q + 1];
memset(r, 0, sizeof r);
for (int i = q - 1; i >= 0; i--) {
if (ops[i][0] == 2)r[i] = find(ops[i][1]) == find(ops[i][2]);
// 初始为朋友关系的两人才能淡化朋友关系
else if (irs.find({ops[i][1], ops[i][2]}) != irs.end())unite(ops[i][1], ops[i][2]);
}
// 输出结果
for (int i = 0; i < q; i++) {
if (ops[i][0] == 2)printf("%s\n", r[i] ? "Yes" : "No");
}
return 0;
}
时间复杂度:O(m+q)
。
空间复杂度:O(m+q)
。
小美的区间删除
题目描述
小美拿到了一个大小为 n
的数组,她希望删除一个区间后,使得剩余所有元素的乘积末尾至少有 k
个 0
。小美想知道,一共有多少种不同的删除方案?
输入描述
第一行输入两个正整数 n,k
。
第二行输入 n
个正整数 a_i
,代表小美拿到的数组。
1≤ n,k ≤ 10^5
1≤ a_i ≤ 10^9
输出描述
一个整数,代表删除的方案数。
解题思路
一个 2
和 一个 5
相乘可得到一个 0
。
代码实现
const int N = 1e5 + 5;
// s2[i] = a[1..i]中包含2的个数
// s5[i] = a[1..i]中包含5的个数
int s2[N], s5[N];
/**
* 统计 x 中包含的 2 和 5 的个数
*/
pair<int, int> count25(int x) {
int c2 = 0, c5 = 0;
while (!(x & 1))c2++, x /= 2;
while (x % 5 == 0)c5++, x /= 5;
return {c2, c5};
}
int main() {
int n, k, x;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &x);
auto [c2, c5] = count25(x);
s2[i] = c2 + s2[i - 1], s5[i] = c5 + s5[i - 1];
}
int l, r;
// 检查 l 和 r 是否不符合要求
auto check = [&] {
return s2[r] - s2[l - 1] > s2[n] - k || s5[r] - s5[l - 1] > s5[n] - k;
};
long long res = 0;
// 枚举每类以 l 为左边界的区间
for (l = 1; l <= n; l++) {
// 找到以 l 为左边界且满足题目条件的最大区间
int i = l, j = n;
while (i < j) {
r = (i + j + 1) >> 1;
if (check())j = r - 1;
else i = r;
}
r = i;
// 若[l,r] 满足条件,则 [l,r-1] 也满足条件,类推
if (!check())res += r - l + 1;
}
printf("%lld", res);
return 0;
}
时间复杂度:O(nlog(n))
。
空间复杂度:O(n)
。
END
温馨提示:由于本平台不能完全支持 Markdown
语法,更好的 Markdown
阅读体验请移步至 A 或 B 或 C。
文章文档:公众号 字节幺零二四
回复关键字可获取本文文档。
题目来源:美团2024年春招第一场笔试【技术】
文章声明:题目来源 牛客 平台,如有侵权,请联系删除!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。