AT_dp_w Intervals 题解
chlchl
·
·
题解
看到题解里多多少少都有错误,我来补一篇,顺便纪念 700AC。
题意简述
给定 m 个形如 l_i,r_i,v_i 的命令,若一个长度为 n 的 01 串中,第 l_i\sim r_i 个位置至少有一个 1,则分数可以增加 v_i。问所有 01 串中的最大分数。
## 做法
对于这种区间的命令,一种常见的做法是化区间为点。具体来说,就是按照右端点排序,当访问到某个区间的右边界时才计算其贡献。
显然,分数的计算只跟 $1$ 的位置有关系,所以状态肯定是 $f_{i,j}$ 表示前 $i$ 个位置最后一个 $1$ 在 $j$ 位置的最大分数。
转移时,一开始我想着先枚举倒数第二个出现 $1$ 的位置 $k$,$f_{i,j}=f_{j-1,k}+\sum_{k<l_x\leq i,r_x=j}v_x$。但这样不仅正确性不保证,即使加上主席树优化,快速算出后面的 $\sum$,时间也去到 $O(n^3\log m)$,肯定过不了。
考虑换一种转移。刚刚枚举倒数第二个 $1$ 出现的位置是不必要的。因为我们的 $j\leq i$,所以前 $i-1$ 个位置的最后一个 $1$ 肯定也是 $j$($j=i$ 时再另说)。
而 $f_{i,j}$ 比 $f_{i-1,j}$ 多的就是右端点在 $i$,左端点包含 $j$ 位置的所有命令的分数。
所以我们得到 $f_{i,j}=f_{i-1,j}+\sum_{l_k\leq j,r_k=i}v_k,j<i$。
当 $j=i$ 时,前 $i-1$ 个位置的最后一个 $1$ 可以是任意位置,所以在 $1\sim i-1$ 中取最大值,即 $f_{i,i}=\max_{j=1}^{i-1}f_{i-1,j}$。
当然,为了防止分数全负,我们还要对 $0$ 取个 $\max$。
时间复杂度 $O(n^3)$,空间复杂度 $O(n^2)$,肯定过不了。
空间优化比较爽,转移只与上一维有关,直接省去 $i$ 维即可。
时间就比较麻烦了。因为这个转移很难优化其中的 $\sum$ 部分。
所以换种角度思考。注意到方程中的 $l_k\leq j$,证明有很多 $f$ 值的更新都用到了同一个命令。我们能不能枚举结尾(即 $i$),对于每个命令,一次多个 $f$ 值来减少复杂度呢?
答案是肯定的。一个命令影响的的是一个连续的区间。我们把 $f$ 数组扔到线段树上,就可以通过命令的区间更新来快速处理了。
线段树的每个单点就表示 $f$ 数组的值,一段区间就表示 $f$ 数组一段区间的最大值。
对于符合 $r_j=i$ 的所有命令,每次将 $[l_j,r_j]$ 这个区间 $+v_j$,相当于将所有 $f_k(l_j\leq k)$ 全部加上当前命令的分数。
查询时直接输出 $1\sim n$ 的最大值即可。
注意,$f_{i,i}$ 的更新是在 $1\sim i-1$ 中取最大值,所以我们要额外做一次单点更新,每一次将 $i$ 这个位置加上 $\max_{j=1}^{i-1}\{f_j\}$。而因为 $i$ 后面的值还没更新,所以直接调用 $val_1$ 就是 $1\sim i-1$ 最大值。
总时间复杂度 $O(n\log n)$,空间复杂度 $O(4n)$,可以 AC。
```cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 10;
int n, m;
struct order{
int l, r;
ll v;
bool operator < (const order &p) const {
return r < p.r;
}
} a[N];
ll val[N << 2], tag[N << 2];
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
void pushdown(int o){
val[ls(o)] += tag[o];
val[rs(o)] += tag[o];
tag[ls(o)] += tag[o];
tag[rs(o)] += tag[o];
tag[o] = 0;
}
void update(int o, int l, int r, int s, int t, ll x){
if(l >= s && r <= t){
val[o] += x;
tag[o] += x;
return ;
}
int mid = (l + r) >> 1;
pushdown(o);
if(s <= mid)
update(ls(o), l, mid, s, t, x);
if(t > mid)
update(rs(o), mid + 1, r, s, t, x);
val[o] = max(val[ls(o)], val[rs(o)]);
}//一课只有更新的线段树
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<=m;i++)
scanf("%d%d%lld", &a[i].l, &a[i].r, &a[i].v);
sort(a + 1, a + 1 + m);//排序,注意不是 n 条命令
for(int i=1,j=1;i<=n;i++){
update(1, 1, n, i, i, max(0ll, val[1]));//更新 i 位置
while(a[j].r == i && j <= m)
update(1, 1, n, a[j].l, a[j].r, a[j].v), j++;//更新线段
//运用双指针思想,均摊 O(1)
}
printf("%lld\n", max(0ll, val[1]));//与 0 取 max
return 0;
}
```