【luogu P5044】meetings 会议(线段树优化DP)(笛卡尔树)
meetings 会议
题目链接:luogu P5044
题目大意
给你一个序列,每次问你一个区间,要你在里面选一个位置,使得区间里面每个位置和你选的位置组成的区间的最大值的和最小。输出这个最小的和。
思路
首先考虑一个 DP,设 \(f_{l,r}\) 为区间 \([l,r]\) 的答案,然后不难根据最大值来搞这么一个东西:
\(f_{l,r}=\min(f_{l,x-1}+(r-x+1)*h_x,(x-l+1)*h_x+f_{x+1,r})\)(\(x\) 为区间最大值的位置,可以通过 ST 表得到)
然后我们可以发现如果这么搞那除了一开始询问的状态,后面的状态要么左端点的左边比整个大,要么右端点的右边比整个大。
而且你会发现这个最大然后分下去就是一棵笛卡尔树!
所以这种边界的个数就是 \(O(n)\) 级别的。
然后我们看询问的状态,我们考虑手动搞第一个操作,那它就分成两个两半,那也就是说我们要对应边界的若干长度的结果。(就比如左边部分我们就要得到后缀的答案,右边部分就是前缀的答案)
但是这样状态又是 \(O(n^2)\) 的了,而且看起来压不了。
那我们就从离线入手,考虑用一个数据结构维护。
那既然要维护就看看怎么转移,那转移是两个情况取最小值。
而且你会发现这两个情况是一个一次函数,而且都是单调递增的,那也就是说我们就在线段树上二分出分界的位置来维护。
然后这颗线段树就是要区间清空,区间加值,区间比较一次函数,这个是可以实现的,具体操作可以看代码。
代码
#include<cstdio>
#include<vector>
#include<iostream>
#define ll long long
using namespace std;
const int N = 750000 + 100;
int n, q, l, r, log2_[N], pl[21][N], maxn[21][N], h[N];
ll ans[N];
struct Ask {
int id, l, r;
};
vector <Ask> Q[N];
int get_max(int l, int r) {
int k = log2_[r - l + 1];
return maxn[k][l] > maxn[k][r - (1 << k) + 1] ? pl[k][l] : pl[k][r - (1 << k) + 1];
}
struct XD_tree {
bool lzy[N << 2];
ll b[N << 2], k[N << 2], lv[N << 2], rv[N << 2];
void up(int now) {
lv[now] = lv[now << 1]; rv[now] = rv[now << 1 | 1];
}
void downc(int now) {
lzy[now] = 1; b[now] = k[now] = lv[now] = rv[now] = 0;
}
void downa(int now, int l, int r, ll b_, ll k_) {
b[now] += b_; k[now] += k_;
lv[now] += k_ * l + b_; rv[now] += k_ * r + b_;
}
void down(int now, int l, int r) {
if (lzy[now]) {
downc(now << 1); downc(now << 1 | 1); lzy[now] = 0;
}
if (b[now] || k[now]) {
int mid = (l + r) >> 1;
downa(now << 1, l, mid, b[now], k[now]); downa(now << 1 | 1, mid + 1, r, b[now], k[now]);
b[now] = k[now] = 0;
}
}
ll query(int now, int l, int r, int pl) {
if (l == r) return lv[now];
down(now, l, r); int mid = (l + r) >> 1;
if (pl <= mid) return query(now << 1, l, mid, pl);
else return query(now << 1 | 1, mid + 1, r, pl);
}
void add(int now, int l, int r, int L, int R, ll va) {//区间加值
if (L <= l && r <= R) {
downa(now, l, r, va, 0); return ;
}
down(now, l, r); int mid = (l + r) >> 1;
if (L <= mid) add(now << 1, l, mid, L, R, va);
if (mid < R) add(now << 1 | 1, mid + 1, r, L, R, va);
up(now);
}
void change(int now, int l, int r, int L, int R, ll b_, ll k_) {//放一条线段
if (L <= l && r <= R) {
ll nl = k_ * l + b_, nr = k_ * r + b_;
if (nl >= lv[now] && nr >= rv[now]) return ;//全部不优
else if (nl <= lv[now] && nr <= rv[now]) {//全部优
downc(now); downa(now, l, r, b_, k_); return ;
}//否则二分下去继续看
}
down(now, l, r); int mid = (l + r) >> 1;
if (L <= mid) change(now << 1, l, mid, L, R, b_, k_);
if (mid < R) change(now << 1 | 1, mid + 1, r, L, R, b_, k_);
up(now);
}
}Tl, Tr;
void slove(int l, int r) {
int mid = get_max(l, r);
if (l < mid) slove(l, mid - 1);
if (mid < r) slove(mid + 1, r);
for (int i = 0; i < Q[mid].size(); i++) {
Ask x = Q[mid][i]; ll lans = 0, rans = 0;
if (x.l < mid) lans = Tr.query(1, 0, n - 1, x.l); lans += 1ll * (x.r - mid + 1) * h[mid];
if (mid < x.r) rans = Tl.query(1, 0, n - 1, x.r); rans += 1ll * (mid - x.l + 1) * h[mid];
ans[x.id] = min(lans, rans);
}
ll suml = h[mid], sumr = h[mid];
if (l < mid) suml += Tl.query(1, 0, n - 1, mid - 1);//前面的值加上这个位置的高度就是这个位置的值
if (mid < r) sumr += Tr.query(1, 0, n - 1, mid + 1);
Tl.add(1, 0, n - 1, mid, mid, suml); Tr.add(1, 0, n - 1, mid, mid, sumr);
if (mid < r) {
Tl.add(1, 0, n - 1, mid + 1, r, 1ll * (mid - l + 1) * h[mid]);
Tl.change(1, 0, n - 1, mid + 1, r, suml - 1ll * mid * h[mid], h[mid]);//它相当于 suml+h[mid]*(x) (x)就是包括里面区间的大小,下面也是同一个道理
}
if (l < mid) {
Tr.add(1, 0, n - 1, l, mid, 1ll * (r - mid + 1) * h[mid]);
Tr.change(1, 0, n - 1, l, mid, sumr + 1ll * mid * h[mid], -h[mid]);
}
}
int main() {
scanf("%d %d", &n, &q);
for (int i = 0; i < n; i++) scanf("%d", &h[i]), maxn[0][i] = h[i], pl[0][i] = i;
log2_[0] = -1; for (int i = 1; i <= n; i++) log2_[i] = log2_[i >> 1] + 1;
for (int i = 1; i <= 20; i++)
for (int j = 0; j + (1 << i) - 1 < n; j++) {
if (maxn[i - 1][j] > maxn[i - 1][j + (1 << (i - 1))]) maxn[i][j] = maxn[i - 1][j], pl[i][j] = pl[i - 1][j];
else maxn[i][j] = maxn[i - 1][j + (1 << (i - 1))], pl[i][j] = pl[i - 1][j + (1 << (i - 1))];
}
for (int i = 1; i <= q; i++) {
scanf("%d %d", &l, &r); int x = get_max(l, r);
Q[x].push_back((Ask){i, l, r});
}
slove(0, n - 1);
for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
return 0;
}