P5044 [IOI2018] meetings 会议

题目链接

写完了。感谢 ywy 学长的讲解!

显然选最大值不优,那么我们可以根据最大值将区间分成两半,分别考虑地点在左右两边的情况,取个最小值即可。用式子来表示就是:

\[f(l,r)=\min(\sum_{i=l}^th_t+f(t+1,r),\sum_{i=t}^rh_t+f(l,t-1)) \]

我们考虑在最值那里计算答案。但是左右端点都不固定不好做,我们可以根据上面那个式子把询问拆成两部分,我们只考虑求右子树内部分的 \(f\),左子树的 \(f\) 可以通过翻转得到。这一步操作的作用是让区间在笛卡尔树上的部分的左边是完整的。同时方便计算答案。

我们现在要维护 \(f_{p,r}\),表示以当前子树根节点 \(p\) 为根,从最左边到 \(r\) 的答案。转移直接用上面的那个式子。不过那个 \(\min\) 很不好搞,考虑怎么把它去掉。发现随着 \(r\) 的增大,右边部分的增量一直是 \(h_t\),而左边部分的增量不会超过 \(h_t\),因此按照这种趋势右边会慢慢超过左边。因此,直接二分即可。

去掉 \(\min\) 以后就很好搞了,发现左边就是在原数组上区间加,右边就是区间赋值成等差数列。用线段树合并做即可。然后又发现实际上左右子树的 DP 数组是不交的,因此直接在一个线段树上搞即可。

不过这样是 \(O(nlog^2n)\) 的,线段树上二分则是 \(O(nlogn)\)。比较考察对线段树掌握的熟练程度。

线段树维护区间加,区间赋值为等差数列,单点查询,线段树上二分:

namespace SGT {
	int ls[NN], rs[NN], root, ttot, len[NN];
	ll val[NN], taga[NN], tagk[NN], tagp[NN];//val : right point's value
	inline void clear() {
		memset(val, 0, sizeof(val));
		memset(tagp, 0, sizeof(tagp));
		for (register int i = 1; i <= ttot; ++i)	taga[i] = tagk[i] = Rand;
	}
	inline void pushup(int cur) {
		val[cur] = val[rs[cur]];
	}
	inline void pushtag(int cur, ll k, ll b) {
		if (!cur)	return ;
		tagp[cur] = 0;
		if (len[cur] == 1)	return val[cur] = k + b, void();
		taga[cur] = b; tagk[cur] = k;
		val[cur] = k * len[cur] + b;
	}
	inline void pushplus(int cur, ll x) {
		if (!cur)	return ;
		val[cur] += x;
		if (taga[cur] != Rand)	return taga[cur] += x, void();//Attention!!
		tagp[cur] += x;
	}
	inline void pushdown(int cur) {
		if (taga[cur] != Rand || tagk[cur] != Rand) {
			pushtag(ls[cur], tagk[cur], taga[cur]);
			pushtag(rs[cur], tagk[cur], taga[cur] + tagk[cur] * len[ls[cur]]);
			taga[cur] = tagk[cur] = Rand;
		}
		if (tagp[cur])	pushplus(ls[cur], tagp[cur]), pushplus(rs[cur], tagp[cur]), tagp[cur] = 0;
	}
	void build(int L, int R, int &cur) {
		cur = ++ttot;
		ls[cur] = rs[cur] = 0;
		len[cur] = R - L + 1; val[cur] = 0; taga[cur] = tagk[cur] = Rand;
		if (L == R)	return ;
		int mid = (L + R) >> 1;
		build(L, mid, ls[cur]), build(mid + 1, R, rs[cur]);
	}
	void modify(int L, int R, int l, int r, ll k, ll b, int cur) {
		if (l <= L && R <= r) { pushtag(cur, k, b); return ; }
		pushdown(cur);
		int mid = (L + R) >> 1;
		if (l <= mid && r > mid) {//Attention!!!!!!!!!!
			modify(L, mid, l, r, k, b, ls[cur]);
			modify(mid + 1, R, l, r, k, b + 1ll * (mid - max(l, L) + 1) * k, rs[cur]);
		} else {
			if (l <= mid)	modify(L, mid, l, r, k, b, ls[cur]);
			if (r > mid)	modify(mid + 1, R, l, r, k, b, rs[cur]);//Attention!!!!!!!!!
		}
		pushup(cur);
	}
	inline void Modify(int l, int r, ll k, ll b) { if (l <= r) modify(1, n, l, r, k, b, root); }
	inline void add(int L, int R, int l, int r, ll x, int cur) {
		if (l <= L && R <= r) { pushplus(cur, x); return ;}
		pushdown(cur);
		int mid = (L + R) >> 1;
		if (l <= mid)	add(L, mid, l, r, x, ls[cur]);
		if (r > mid)	add(mid + 1, R, l, r, x, rs[cur]);
		pushup(cur);
	}
	inline void Add(int l, int r, ll x) { if (l <= r)	add(1, n, l, r, x, root); }
	ll query(int L, int R, int pos, int cur) {
		if (L == R)	return val[cur];
		pushdown(cur);
		int mid = (L + R) >> 1;
		if (pos <= mid)	return query(L, mid, pos, ls[cur]);
		return query(mid + 1, R, pos, rs[cur]);
	}
	inline ll Query(int p) { return query(1, n, p, root); }
	int dfs(int L, int R, int l, int r, ll k, ll b, int cur) {//线段树上二分
		if (len[cur] == 1)	return val[cur] >= k + b ? L : L - 1;
		pushdown(cur);
		int mid = (L + R) >> 1;
		if (l <= L && R <= r) {//Attention!!!
			if (val[ls[cur]] >= k * len[ls[cur]] + b)
				return dfs(mid + 1, R, l, r, k, k * len[ls[cur]] + b, rs[cur]);
			return dfs(L, mid, l, r, k, b, ls[cur]);
		}
		if (l <= mid && r > mid) {//Attention!!!!!
			if (val[ls[cur]] >= k * (mid - max(l,L) + 1) + b)//Attention!!!!!
				return dfs(mid + 1, R, l, r, k, k * (mid - max(l, L) + 1) + b, rs[cur]);
			return dfs(L, mid, l, r, k, b, ls[cur]);
		}
		if (l <= mid)	return dfs(L, mid, l, r, k, b, ls[cur]);
		return dfs(mid + 1, R, l, r, k, b, rs[cur]);
	}
	inline int Dfs(int l, int r, ll k, ll b) { return dfs(1, n, l, r, k, b, root); }
}

笛卡尔树的构建及笛卡尔树上 DP 回答询问:

struct Queries {
	int l, r, t;
	ll res, ans;
	Queries() { l = r = t = 0; res = 0; ans = inf; }//Attention!!!
	inline void calc() {
		ans = min(ans, res + 1ll * h[t] * (t - l + 1));
	}
	inline void cg() {
		l = n - l + 1, r = n - r + 1;
		swap(l, r);
		res = 0;
	}
}qs[N];
vector<int> vec[N];

int ls[N], rs[N], stk[N], stop, Rt, tl[N], tr[N];
int dep[N], siz[N], fa[N], son[N];
inline void Clear()
void dfs_son(int cur, int faa)
int top[N];
void dfs_chain(int cur, int topp)
inline int get_lca(int x, int y)

inline void init() {
	for (register int i = 1; i <= n; ++i) {
		while (stop && h[i] > h[stk[stop]])	ls[i] = stk[stop], --stop;
		if (stop)	rs[stk[stop]] = i;
		stk[++stop] = i;
	}
	Rt = stk[1];
	dfs_son(Rt, 0);
	dfs_chain(Rt, Rt);
}

void dfs(int cur) {
	if (!cur)	return ;
	dfs(ls[cur]); dfs(rs[cur]);
	if (!ls[cur] && !rs[cur]) {
		SGT::Modify(cur, cur, 0, h[cur]);
	} else if (!ls[cur]) {
		SGT::Add(cur, tr[cur], h[cur]);
	} else if (!rs[cur]) {
		SGT::Modify(cur, cur, 0, SGT::Query(cur - 1) + h[cur]);//Attention!!!!!!
	} else {
		ll tmp = SGT::Query(cur - 1);
		int pos = SGT::Dfs(cur + 1, tr[cur], h[cur], tmp - 1ll * h[cur] * (cur - tl[cur]));
		SGT::Modify(cur, pos, h[cur], tmp);
		SGT::Add(pos + 1, tr[cur], 1ll * (cur - tl[cur] + 1) * h[cur]);//Attention!!!!
	}
	for (register unsigned int i = 0; i < vec[cur].size(); ++i) {//Attention!!!!!
		int nw = vec[cur][i];
		int r = qs[nw].r;
		qs[nw].res = SGT::Query(r);
	}
	vec[cur].clear();
}
posted @ 2020-08-28 12:56  JiaZP  阅读(231)  评论(0编辑  收藏  举报