视频加载失败

安吉D10-T3

1571 字
8 分钟
安吉D10-T3
原题呈现

我爱喝奶茶#

题目背景#

小信家附近开了一家奶茶店,从此他迷上了喝奶茶.

题目描述#

奶茶店共有 mm 款特色限定奶茶,编号为 1,2,...,m1,2,...,m,第 ii 款奶茶给小信带来的愉悦度为 wiw_i

nn 天之中,奶茶店每天只会上架其中一款限定奶茶,第 ii 天上架的是第 fif_i 款.

小信可以选择打卡连续的 [l,r][l,r] 天内上架的所有奶茶.

但是小信是一个非常喜新厌旧的人,一旦同一款奶茶打卡多于一次,就会厌恶这款奶茶,甚至忘记奶茶曾经带给他的快乐(即仅当该款奶茶在区间内恰好只打卡过 1 次时,才能获得其愉悦程度 wiw_i).

现在,请问小信怎么打卡能让他的愉悦程度最大,最大的愉悦度是多少.

输入格式#

第一行两个整数 n,mn,m

第二行包含 nn 个整数,第 ii 个表示 fif_i

第三行包含 mm 个整数,第 ii 个表示 wiw_i

输出格式#

一行三个整数,分别表示打卡的区间 [l,r][l,r],以及最大打卡收益,用空格隔开.如果有多个区间满足条件,输出区间长度最短且 ll 最小的一个.

样例#

样例输入#

9 4
2 3 1 1 4 1 2 4 1
5 3 6 6

样例输出#

1 5 15

数据范围#

对于 20pts20\text{pts} 的数据:0n,m5000 \le n,m \le 500

对于 60pts60\text{pts} 的数据:0n,m1040 \le n,m \le 10^4 其中有 10pts10\text{pts} 的数据满足每种奶茶最多出现两次

对于 100pts100\text{pts} 的数据:0n,m1060 \le n,m \le 10^61fim1 \le f_i \le m1wi1091 \le w_i \le 10^9

我们枚举起点,对于每一个起点 ii,对从第 ii 天起的每一天的奶茶的权值做一次前缀和,其中,第二次出现的奶茶权值为 w[i]-w[i],第三次出现的奶茶权值为 00

如下面的例子:

9 4
2 3 1 1 4 1 2 4 1
1 2 3 4

则以第一天为起点每一天出现的奶茶的权值列表为:

2 3 1 1 4 1 2 4 1

将其中第二次出现的奶茶权值置为 w[i]-w[i],第三次出现的奶茶权值置为 00,则可得:

2 3 1 -1 4 0 -2 -4 0

将其做一次前缀和:

2 5 6 5 9 9 7 3 3

前缀和数组中最大的值(设其下标为 kk,此例中 k=5k=5)即为起点为 1 时的最大答案,区间为 [1,k][1,k]

再如,第二天为起点每一天出现的奶茶的权值列表为:

3 1 1 4 1 2 4 1

将其中第二次出现的奶茶权值置为 w[i]-w[i],第三次出现的奶茶权值置为 00,则可得:

3 1 -1 4 0 2 -4 0

将其做一次前缀和:

3 4 3 7 7 9 5 5

前缀和数组中最大的值(设其下标为 kk,此例中 k=6k=6)即为起点为 2 时的最大答案,区间为 [2,k][2,k]

对于每一天作为起点都这么操作一次,取起点为 [1,n][1,n] 答案的最大值,时间复杂度为 O(n2)O(n^2),考虑优化.

枚举起点好像没有优化空间,但是如何快速处理前缀和呢?

仔细观察原数组在 ii+1i\rightarrow i+1 时有什么变化:

  • 原数组中第一个奶茶种类为 a[i] 的奶茶(设其下标为 ii)消失了.
  • 原数组中第二个奶茶种类为 a[i] 的奶茶(设其下标为 nxtinxt_i)权值取了相反数,变为 w[a[i]]
  • 原数组中第三个奶茶种类为 a[i] 的奶茶(设其下标为 nxtnxtinxt_{nxt_i})权值从 0 变为 -w[a[i]]

对应到前缀和数组(设大小为 nn)上就是:

  • 区间 [i,n][i,n] 中每一项减少 w[a[i]]
  • 区间 [nxti,n][nxt_i, n] 中每一项增加 2*w[a[i]]
  • 区间 [nxtnxti,n][nxt_{nxt_i}, n] 中每一项减少 w[a[i]]

区间修改使用线段树优化,就可以在 O(logn)O(\log n) 的复杂度内更新前缀和数组.

至于区间,只需要在线段树上二分查找:

  • 对于每一个线段树上的节点,比较其两个子节点的 max 哪一个更大,选择更大的那个节点,在该子节点中继续查找,直到找到大小为 1 的节点.

标程

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e6 + 100;
int n, m, a[N], w[N], l, r;
ll s[N];
ll ans = -1;
vector<int> vec[N];
int pointer[N];
class SegmentTree {
public:
struct Node {
int l, r;
ll max;
ll add;
} f[N * 5];
void Build(int t, int l, int r) {
f[t].l = l;
f[t].r = r;
if (l == r) {
f[t].max = s[l];
return;
}
int mid = (l + r) / 2;
Build(t * 2, l, mid);
Build(t * 2 + 1, mid + 1, r);
pushup(t);
}
void Add(int t, int x, int y, ll v) {
pushdown(t);
if (x <= f[t].l && f[t].r <= y) {
f[t].add += v;
f[t].max += v;
return;
}
if (x > f[t].r || y < f[t].l) {
return;
}
Add(t * 2, x, y, v);
Add(t * 2 + 1, x, y, v);
pushup(t);
}
ll Query(int t, int x, int y) {
pushdown(t);
if (x <= f[t].l && f[t].r <= y) {
return f[t].max;
}
if (x > f[t].r || y < f[t].l) {
return -1;
}
return max(Query(t * 2, x, y), Query(t * 2 + 1, x, y));
}
int findmax(int t) {
if (f[t].l == f[t].r) {
return f[t].l;
}
if (Query(t * 2, 1, n) >= Query(t * 2 + 1, 1, n)) {
return findmax(t * 2);
}
return findmax(t * 2 + 1);
}
private:
void pushup(int t) { f[t].max = max(f[t * 2].max, f[t * 2 + 1].max); }
void pushdown(int t) {
if (f[t].add) {
ll add = f[t].add;
f[t * 2].add += add;
f[t * 2].max += add;
f[t * 2 + 1].add += add;
f[t * 2 + 1].max += add;
f[t].add = 0;
}
}
} tr;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++) {
cin >> w[i];
}
for (int i = 1; i <= n; i++) {
vec[a[i]].push_back(i);
int wt = w[a[i]];
if (vec[a[i]].size() == 2) {
wt = -w[a[i]];
}
if (vec[a[i]].size() > 2) {
wt = 0;
}
s[i] = s[i - 1] + wt;
}
tr.Build(1, 1, n);
for (int i = 1; i <= n; i++) {
ll tot = tr.Query(1, 1, n);
int newR = tr.findmax(1);
int nowLen = newR - i + 1;
int miniLen = r - l + 1;
if (tot > ans || (tot == ans && nowLen < miniLen)) {
ans = tot;
l = i;
r = newR;
}
int wt = w[a[i]];
tr.Add(1, i, n, -wt);
pointer[a[i]]++;
if (pointer[a[i]] != vec[a[i]].size()) {
int top = vec[a[i]][pointer[a[i]]];
tr.Add(1, top, n, 2ll * wt);
pointer[a[i]]++;
if (pointer[a[i]] != vec[a[i]].size()) {
int top = vec[a[i]][pointer[a[i]]];
tr.Add(1, top, n, -wt);
}
pointer[a[i]]--;
}
}
cout << l << " " << r << " " << ans;
return 0;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D10-T3
https://blog.jerrylab.top/posts/problem/anji2026/D10/T3/
作者
Jerry
发布于
2026-08-10
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0