题目描述
难度分:2000
输入n(1≤n≤105),q(1≤q≤500)和长为n的数组v(−105≤v[i]≤105),长为n的数组c(1≤c[i]≤n)。
有n个球排成一排,第i个球的价值为v[i],颜色为c[i]。
然后输入q个询问,每个询问输入a和b,范围[−105,105]。对每个询问,计算:
从这一排球中,选出一个子序列,子序列的价值和为:
- 如果球不在序列开头,且球的颜色与前一个球的颜色相同,则加上球的值×a。
- 否则,加上球的值×b。
输出子序列的价值和的最大值。子序列可以是空的,此时价值和为 0。
注:子序列不一定连续。
输入样例1
6 3
1 -2 3 4 0 -1
1 2 1 2 1 1
5 1
-2 1
1 0
输出样例1
20
9
4
输入样例2
4 1
-3 6 -1 2
1 2 3 1
1 -1
输出样例2
5
算法
动态规划
想了好久贪心没想出来怎么贪,所以感觉还是只能DP
。
状态定义
f[i][c]表示遍历到i位置,前一个颜色为c,考虑[i,n]中选球能够得到的最大价值。可以发现这样开空间的话,i∈[1,n],c∈[1,n]直接就MLE
了。考虑到我们DP
是从前往后DP
,当遍历到i位置时前面的信息已经有了,所以第一维i可以优化掉。
状态转移
当前可以选择ci这个颜色的球,如果前面一个球的颜色不是ci,假设为cp(前面没有球就看做cp=0,初始化f[0]=0,其他颜色的DP
值都是负无穷),则有状态转移方程f[ci]=maxcp≠cif[cp]+b×vi。
如果前一个球颜色就是ci,则有状态转移方程f[ci]=f[ci]+max(0,a×vi)。
维护top2最值的优化
这个套路ABC也出过,一定要学会!
此时发现是动态求区间最大值的操作,想用线段树来存f数组,但是很遗憾,这个数据量下加个log会TLE
,所以还是得优化成线性的时间复杂度。发现转移后f数组中的值是单调不减的,我们只需要保存前面最大值的颜色和次大值的颜色,就可以在O(1)的时间复杂度下完成状态转移。
复杂度分析
时间复杂度
状态数量为O(n),单次转移的时间复杂度为O(1),因此每次动态规划的时间复杂度为O(n),q次询问的总时间复杂度为O(nq)。
空间复杂度
除了输入的v数组和c数组,空间消耗只有DP
数组f,它的大小只和颜色数量n有关,是线性的,因此额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, q, v[N], c[N];
LL f[N];
int main() {
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; i++) {
scanf("%d", &v[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &c[i]);
}
while(q--) {
LL a, b;
scanf("%lld%lld", &a, &b);
for(int i = 1; i <= n; i++) {
f[i] = -INF;
}
LL ans = 0;
int pre = 0, prepre = 0;
for(int i = 1; i <= n; i++) {
LL res = max(b*v[i], f[c[i]] + a*v[i]);
if(c[i] != pre) {
res = max(res, f[pre] + b*v[i]);
}else {
res = max(res, f[prepre] + b*v[i]);
}
if(res > f[pre]) {
if(pre != c[i]) {
prepre = pre;
pre = c[i];
}else {
pre = c[i];
}
}else {
if(res > f[prepre] && c[i] != pre) {
prepre = c[i];
}
}
f[c[i]] = max(f[c[i]], res);
ans = max(ans, res);
}
printf("%lld\n", ans);
}
return 0;
}