题目描述
难度分:2500
输入n(1≤n≤5×105),长为n的数组a(1≤a[i]≤n),长为n的数组 cost(−109≤cost[i]≤109)。
然后输入m(1≤m≤n)和长为m的严格递增数组b(1≤b[i]≤n)。
定义record(arr)是arr的一个严格递增子序列,它的构造过程如下:
- 首先初始化子序列为[arr[0]]。
- 继续向后遍历,如果arr[i]比子序列的最后一个数大,就加到子序列的末尾。
例如record([3,1,2,7,7,3,6,7,8])=[3,7,8]。
你可以删除a的一些数,删除a[i]的花费是cost[i]。
你需要让record(a)等于b。
如果无法做到,输出NO
;否则输出YES
和最小花费。
输入样例1
11
4 1 3 3 7 8 7 9 10 7 11
3 5 0 -2 5 3 6 7 8 2 4
3
3 7 10
输出样例1
YES
20
输入样例2
6
2 1 5 3 6 5
3 -9 0 16 22 -14
4
2 3 5 6
输出样例2
NO
算法
树状数组优化DP
如果b不是a的子序列,肯定无解;否则一定是有解的,利用动态规划来求解。
状态定义
fi,j表示从a的子数组[1,i]中选出一个以ai结尾的子序列,且这个子序列能够与b的前缀[1,j]匹配的情况下,子序列对应p的最大权值和。
由于b是个严格递增序列,所以当匹配的子序列确定时,j也是确定的。因此,可以优化掉状态f中的j状态,再做个哨兵a0=b0=0。
状态转移
根据以上的状态定义,有状态转移方程
fi=maxk∈[0,i),ak=bj−1(fk+Σi−1t=k+1pt)+pi
其中t还需要满足,at≤bj−1,否则ai不能直接接在ak之后。且pt>0,这是一个出于贪心的考虑,为的是让fi尽可能大。
设gk=fk+Σi−1t=k+1pt,那么fi=maxi−1k=0gk+pi。考虑fi+1状态的gk变化,此时只是Σi−1t=k+1pt部分多了个pi(t∈[k+1,i])。对于k∈[0,i),如果pi≤0,那gk肯定就不变了;否则所有满足ak≥ai的gk都会加上pi;而对于k=i的gk,后面的求和起点大于终点,和不存在,有gk=fi成立。
由于求的是一个前缀最值,而不是任意区间的最值,所以不一定要用线段树来实现,可以用ak作为下标开树状数组,下标v处的值为maxk∈[0,i),ak=vgk,这样就能O(log2n)进行状态转移计算fi。动态规划完成之后,只是求出了保留下标的最大值mx,要删除下标的最小代价应该为Σni=1p[i]−mx。
复杂度分析
时间复杂度
遍历i∈[1,n]计算f[i]时间复杂度是O(n)。单次状态转移需要先对b进行二分查找,时间复杂度为O(log2m),然后操作树状数组进行转移,而树状数组的范围是O(n)级别的,因此操作树状数组的时间复杂度为O(log2n)。而m≤n,因此算法总体的时间复杂度为O(nlog2n)。
空间复杂度
除了输入的a、b、p数组,额外空间的瓶颈在于树状数组,空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 500010;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m, a[N], b[N], p[N];
LL f[N];
// 注意本题的树状数组充当的是个差分数组,以单点加来实现区间加
struct BIT{
LL c[N];
void add(int x, LL v) {
for(; x <= n; x += lowbit(x)) {
c[x] += v;
}
}
LL query(int x) { // 单点查询
LL s = 0;
for(; x; x -= lowbit(x)) {
s += c[x];
}
return s;
}
int lowbit(int x) {
return x&-x;
}
} tr;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
LL tot = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &p[i]);
tot += p[i];
}
scanf("%d", &m);
for(int i = 1; i<= m; i++) {
scanf("%d", &b[i]);
}
tr.add(1, -INF);
for(int i = 1; i <= n; ++i) {
int j = lower_bound(b + 1, b + 1 + m, a[i]) - b;
if(j <= m && a[i] == b[j]) {
// a[i]需要保留,状态转移计算f[i]
if(j == 1) {
f[i] = p[i];
}else {
f[i] = tr.query(b[j - 1]) + p[i];
}
}else {
f[i] = -INF;
}
if(p[i] > 0) {
tr.add(a[i], p[i]); // 所有a[k]>=a[i]的g[k]要加上p[i],在差分数组的左端点上加
}
// 更新最大值
LL temp = tr.query(a[i]);
if(f[i] > temp) {
// 最大值更大了,更新区间的左右端点
tr.add(a[i], f[i] - temp);
tr.add(a[i] + 1, temp - f[i]);
}
}
LL ans = tr.query(b[m]);
if(ans > -1e15) {
puts("YES");
printf("%lld\n", tot - ans);
} else {
puts("NO");
}
return 0;
}