茶6.3-6.7

茶6.3-6.7

Mon

Money Trees_898F

输入 T(≤1e3) 表示 T 组数据。所有数据的 n 之和 ≤2e5。
每组数据输入 n(1≤n≤2e5) k(1≤k≤1e9) 和长为 n 的数组 a(1≤a[i]≤1e4),长为 n 的数组 h(1≤h[i]≤1e9)。

选一个子区间 [L,R],对于 [L,R-1] 内的 i,满足 h[i] 是 h[i+1] 的倍数,且 a[L] + a[L+1] + … + a[R] 不超过 k。
输出子区间长度 R-L+1 的最大值。
如果没有这样的子区间,输出 0。

输入
5
5 12
3 2 4 1 8
4 4 2 4 1
4 8
5 4 1 2
6 2 3 1
3 12
7 9 10
2 2 4
1 10
11
1
7 10
2 6 3 1 5 10 6
72 24 24 12 4 4 2
输出
3
2
1
0
3

Sol:

分组循环+不定长滑窗。

分组循环求满足 h[i] 能被 h[i+1] 整除的组。

每组内用滑窗求元素和不超过 k 的最长子数组。 分组循环

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void solve()
{
int n, k;
cin >> n >> k;
vi a(n),b(n);
for(int i = 0; i < n; i++) cin >> a[i];
for(int i = 0; i < n; i++) cin >> b[i];
int ans = 0;
for(int i = 0; i < n;) {
int st = i;
for(i++; i < n && b[i - 1] % b[i] == 0; i++) {
}
//cout << "st:"<< st <<" i :"<< i << endl;
vi c(a.begin() + st, a.begin() + i);
int l = 0, s = 0;
for (int r = 0; r < c.size(); r++) {
s += c[r];
while (s > k) {
s -= c[l];
l++;
}
ans = max(ans, r - l + 1);
}
}
cout << ans << endl;
}

Tue

Liebig’s Barrels_985C

输入 n k(1≤n,k≤1e5 且 1≤nk≤1e5) L(0≤L≤1e9) 和长为 nk 的数组 a(1≤a[i]≤1e9),表示 n*k 条木板的长度。

你要制作 n 个木桶,每个木桶需要 k 条木板。
你可以使用任意 k 条木板来组装一个木桶,每条木板必须恰好属于一个木桶。
一个木桶的体积,等于其最短木板的长度。

要求:任意一对木桶的体积差都不能超过 L。
输出所有木桶体积和的最大值。
如果无法满足,输出 0。

输入
4 2 1
2 2 1 2 3 2 2 3
输出 7

输入
2 1 0
10 10
输出 20

输入
1 2 1
5 2
输出 2

输入
3 2 1
1 2 3 4 5 6
输出 0

Sol:

贪心,先将木板排序,最小的木桶肯定是a[0],那么我们最多能选到长度为a[0]+l的木板。如果数量不足n个,则无法满足,若是多余n个,则可以让小的木桶带几个小的木板,以增加最终体积的最大值。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void solve()
{
int n,k,l;
cin>>n>>k>>l;
vi a(n*k);
for(int i = 0;i < n*k;i++) cin>>a[i];
sort(a);
int rg = int(upper_bound(a.begin(), a.end(), a[0] + l) - a.begin());
if (rg < n) {
cout << "0" << endl;
return;
}
int u = 0;
ll ans = 0;
for(int i = 0; i < n; i++) {
ans += a[u++];
for(int j = 0;j < k - 1; j++) {
if (rg - u > n - i - 1) {
u++;
} else {
break;
}
}
}
cout << ans << endl;
}

Wed

Alarm Clock_451D

输入 n(1≤n≤2e5) m(1≤m≤1e6) k(1≤k≤n) 和长为 n 的数组 a(1≤a[i]≤1e6)。

数轴上有 n 个点,要求任意长为 m-1 的闭区间不能包含 >= k 个点。
问:至少要去掉多少个点?

注:闭区间 [L,R] 的长度为 R-L。

输入
3 3 2
3 5 1
输出 1

输入
5 10 3
12 8 18 25 1
输出 0

输入
7 7 2
7 3 4 1 6 5 2
输出 6

输入
2 2 2
1 3
输出 0

Sol:

首先,我们需要按照时间递增的顺序对所有闹钟进行排序。我们还将使用 queue 来存储闹钟时间。

我们将从第一个警报开始迭代。设当前闹钟时间等于x。在 set 不为空并且第一个集合元素小于 x - m + 1 之前,我们应该删除第一个集合元素。此后,只有时间不早于相对 x 的 m - 1 分钟的警报才会被设置。如果之后集合大小小于 k - 1,我们应该在集合中插入 x (我们不会关闭此警报)。在另一种情况下,我们应该关闭此警报,因此我们增加一个答案,并且不在集合中插入 x。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void solve()
{
int n, m, k;
cin >> n >> m >> k;
vi a(n);
FOR(i, 0, n-1) cin >> a[i];
sort(a);
int ans = 0;
queue<int> q;
FOR(i, 0, n-1) {
int now = a[i];
while (!q.empty() && q.front() < now - m + 1) {
q.pop();
}
if (q.size() >= k-1) {
ans++;
} else {
q.push(now);
}
}
cout << ans << endl;
}

Thu

Choosing Balls_162C

输入 n(1≤n≤1e5) q(1≤q≤500) 和长为 n 的数组 v(-1e5≤v[i]≤1e5),长为 n 的数组 c(1≤c[i]≤n)。
有 n 个球排成一排,第 i 个球的价值为 v[i],颜色为 c[i]。

然后输入 q 个询问,每个询问输入 a 和 b,范围 [-1e5,1e5]。
对每个询问,计算:
从这一排球中,选出一个子序列,子序列的价值和为:

  • 如果球不在序列开头,且球的颜色与前一个球的颜色相同,则加上球的值 * a。
  • 否则,加上球的值 * b。
    输出子序列的价值和的最大值。
    子序列可以是空的,此时价值和为 0。

注:子序列不一定连续。

输入
6 3
1 -2 3 4 0 -1
1 2 1 2 1 1
5 1
-2 1
1 0
输出
20
9
4

输入
4 1
-3 6 -1 2
1 2 3 1
1 -1
输出
5

Sol:

每个询问跑一个 O(n) 的子序列 DP。

提示:想一想,如果只有两种颜色,要如何 DP?

遍历到第 i 个球时,我们需要维护以颜色 c 结尾的子序列的最大价值和,定义为 f[c],初始全为 -inf。

对于每个球,有四种决策:

  1. 不选:f[c] 不变。

  2. 选,且和前一个球颜色一样:f[c] += v*a。

  3. 选,且和前一个球颜色不一样:我们需要知道颜色不等于 c 的最大 f,记作 f[mxC]。具体分析见下。

  4. 选,作为子序列第一个数:f[c] = v*b。

对于第三个决策,我们需要维护 f 的最大值 mx1、最大值对应的颜色 mxC,以及 f 的颜色不等于 mxC 的最大值 mx2。 如果 c != mxC,那么 f[c] = mx1 + v b,否则 f[c] = mx2 + v b。 最后答案为 mx1。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void solve()
{
int n, q, a, b, mx;
cin >> n >> q;
vector<pii> ps(n);
for (int i = 0; i < n; i++) {
cin >> ps[i].first;
}
for (int i = 0; i < n; i++) {
cin >> ps[i].second;
}
vi f(n + 1, 0);
for (int i = 0; i < q; i++) {
cin >> a >> b;
fill(all(f), -1e18);
int mx1 = 0, mx2 = 0, mxC = 0;
for (int j = 0; j < n; j++) {
int c = ps[j].second;
if (c != mxC) {
mx = mx1;
} else {
mx = mx2;
}
f[c] = max(f[c] + max(ps[j].first * a, 0), mx + ps[j].first * b);
if (f[c] > mx1) {
if (c != mxC) {
mx2 = mx1;
mxC = c;
}
mx1 = f[c];
} else if (c != mxC && f[c] > mx2) {
mx2 = f[c];
}
}
cout << mx1 << endl;
}
}

Fri

https://codeforces.com/problemset/problem/650/D

输入 n(1≤n≤4e5) m(1≤m≤4e5) 和长为 n 的数组 a(1≤a[i]≤1e9),数组下标从 1 开始。
然后输入 m 个询问,每个询问输入两个数 i(1≤i≤n) 和 b(1≤b≤1e9)。

对于每个询问,输出把 a[i] 替换成 b 后,a 的最长严格递增子序列(LIS)的长度。
注意每个询问之间彼此独立,比如第一个询问把 a[1] 替换成 6,那么对于第二个询问,a[1] 还是原来的值。

输入
4 4
1 2 3 4
1 1
1 4
4 3
4 5
输出
4
3
3
4

输入
4 2
1 3 2 6
3 5
2 4
输出
4
3

Sol:

上周五的 CF486E,需要用到这题的结论。

修改后的 LIS,有两种情况:

  1. LIS 不包含修改后的 a[i],那么计算的就是原数组去掉 a[i] 后的 LIS 长度。
  2. LIS 包含修改后的 a[i]。 这两种情况取 max 即为答案。

第一种情况的计算方法:

设原数组的 LIS 长度为 k。

用上周五(CF486E)的结论,如果原数组的 a[i] 在所有 LIS 中,则去掉 a[i] 后,LIS 长度缩小,即 k-1;否则 LIS 长度不变,仍然为 k。

注意,为了判断这件事,需要正着计算一遍所有前缀的 LIS 长度,再反着计算所有后缀的 LIS 长度。

第二种情况的计算方法:

(先把询问离线,按照修改的下标分组)

上面说到,我们需要计算所有前缀/后缀的 LIS 长度,这用的是二分算法,会用到一个辅助数组 g,其中 g[i] 等于长为 i 的上升子序列的末尾元素的最小值。

正序遍历到 a[i] 时,先对所有在 i 处的询问,在 g 数组上二分查找 b,算出 LIS 中小于 b 的元素个数,加到答案中,然后答案再加一(把 b 算进去)。

倒序遍历到 a[i] 时,先对所有在 i 处的询问,在 g 数组上二分查找 b,算出 LIS 中大于 b 的元素个数,加到答案中。 这样我们就得到了包含 a[i]=b 的 LIS 长度。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void solve()
{
int n, m;
cin >> n >> m;
vi a(n);
FOR(i, 0, n-1) cin >> a[i];
vector<vector<pair<int, int> > > qs(n);
for (int i = 0; i < m; i++) {
int j, v;
cin >> j >> v;
qs[j-1].push_back({i, v});
}
vector<int> ans(m);
vector<int> pre(n);
vector<int> g;
for (int i = 0; i < n; i++) {
for (auto p : qs[i]) {
ans[p.first] = lower_bound(g.begin(), g.end(), p.second) - g.begin() + 1;
}
int p = lower_bound(g.begin(), g.end(), a[i]) - g.begin();
if (p < g.size()) {
g[p] = a[i];
} else {
g.push_back(a[i]);
}
pre[i] = p + 1;
}
vector<int> suf(n);
g.clear();
for (int i = n - 1; i >= 0; i--) {
for (auto p : qs[i]) {
ans[p.first] += lower_bound(g.begin(), g.end(), -p.second) - g.begin();
}
int v = -a[i];
int p = lower_bound(g.begin(), g.end(), v) - g.begin();
if (p < g.size()) {
g[p] = v;
} else {
g.push_back(v);
}
suf[i] = p + 1;
}
int lis = g.size();
vector<int> cnt(n+1);
for (int i = 0; i < n; i++) {
if (pre[i] + suf[i] - 1 == lis) {
cnt[pre[i]]++;
}
}
for (int i = 0; i < n; i++) {
int k = lis;
if (pre[i] + suf[i] - 1 == lis && cnt[pre[i]] == 1) {
k--;
}
for (auto p : qs[i]) {
ans[p.first] = max(ans[p.first], k);
}
}
for (int v : ans) {
cout << v << endl;
}
}