【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;
} 
posted @ 2022-04-08 21:02  あおいSakura  阅读(35)  评论(0编辑  收藏  举报