UOJ#410/LOJ2868【IOI2018】会议 笛卡尔树+线段树

Description
N N N 座山横着排成一行,从左到右编号为从 0 0 0 N − 1 N−1 N1。山 i i i 的高度为 H i ( 0 ≤ i ≤ N − 1 ) Hi( 0≤i≤N−1 ) Hi0iN1。每座山的顶上恰好住着一个人。

你打算举行 Q Q Q 个会议,编号为从 0 0 0 Q − 1 Q−1 Q1。会议 j ( 0 ≤ j ≤ Q − 1 ) j( 0≤j≤Q−1 ) j0jQ1的参加者为住在从山 L j Lj Lj 到山 R j Rj Rj(包括 LjLj 和 RjRj )上的人 ( 0 ≤ L j ≤ R j ≤ N − 1 ) ( 0≤Lj≤Rj≤N−1 ) 0LjRjN1。对于该会议,你必须选择某个山 x x x 做为会议举办地 ( L j ≤ x ≤ R j ) ( Lj≤x≤Rj ) LjxRj。举办该会议的成本与你的选择有关,其计算方式如下:

来自每座山 y ( L j ≤ y ≤ R j ) y(Lj≤y≤Rj) yLjyRj的参会者的成本,等于在山 x x x 和 yy 之间(包含 x x x y y y )的所有山的最大高度。特别地,来自山 x x x 的参会者的成本是 H x Hx Hx,也就是山 x x x 的高度。
会议的成本等于其所有参会者的成本之和。
你想要用最低的成本来举办每个会议。

注意,所有的参会者将在每次会议后回到他们自己的山;所以一个会议的成本不会受到先前会议的影响。


Sample Input
4 2
2 4 3 5
0 2
1 3


Sample Output
10
12


L O J LOJ LOJ可以以传统方式提交。

我们考虑离线完成这个问题,我们把一个询问挂在这个区间的最大值上。
设区间 [ l , r ] [l,r] [l,r]的答案为 f [ l ] [ r ] f[l][r] f[l][r],最大值的位置为 m i d mid mid
那么 f [ l ] [ r ] = m i n ( f [ l ] [ m i d − 1 ] + ( r − m i d + 1 ) ∗ h [ m i d ] , f [ m i d + 1 ] [ r ] + ( m i d − l + 1 ) ∗ h [ m i d ] ) f[l][r]=min(f[l][mid-1]+(r-mid+1)*h[mid],f[mid+1][r]+(mid-l+1)*h[mid]) f[l][r]=min(f[l][mid1]+(rmid+1)h[mid],f[mid+1][r]+(midl+1)h[mid])
那么这个结构就是一棵笛卡尔树。
对于每一个右端点,或左端点,我们考虑维护他到当前分治中心的答案。
左端点或右端点的问题你可以把这个串正反跑两遍,相当于你每次只用维护某个点作为右端点到分治中心的答案即可。

考虑更新某一段区间到分治中心的答案。
设我当前区间为 [ l , r ] [l,r] [l,r],分治中心为 [ l , r ] [l,r] [l,r]
那么我们对于线段树上的 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]中的每一个 i i i都要让他们的值变成:
f [ l ] [ i ] = m i n ( f [ l ] [ m i d − 1 ] + ( i − m i d + 1 ) ∗ h [ m i d ] , f [ m i d + 1 ] [ i ] + ( m i d − l + 1 ) ∗ h [ m i d ] ) f[l][i]=min(f[l][mid-1]+(i-mid+1)*h[mid],f[mid+1][i]+(mid-l+1)*h[mid]) f[l][i]=min(f[l][mid1]+(imid+1)h[mid],f[mid+1][i]+(midl+1)h[mid])
事实上这个区间中肯定会以一个点作为分界线,满足左边的值都取 f [ l ] [ m i d − 1 ] + ( r − m i d + 1 ) ∗ h [ m i d ] f[l][mid-1]+(r-mid+1)*h[mid] f[l][mid1]+(rmid+1)h[mid],右边的值都取 f [ m i d + 1 ] [ i ] + ( m i d − l + 1 ) ∗ h [ m i d ] f[mid+1][i]+(mid-l+1)*h[mid] f[mid+1][i]+(midl+1)h[mid]
那么可以通过在线段树上二分实现这个修改。

考虑证明上面的结论:
f [ l ] [ m i d − 1 ] + ( i − m i d + 1 ) ∗ h [ m i d ] − f [ m i d + 1 ] [ i ] − ( m i d − l + 1 ) ∗ h [ m i d ] &lt; = f [ l ] [ m i d − 1 ] + ( ( i + 1 ) − m i d + 1 ) ∗ h [ m i d ] − f [ m i d + 1 ] [ i + 1 ] − ( m i d − l + 1 ) ∗ h [ m i d ] f[l][mid-1]+(i-mid+1)*h[mid]-f[mid+1][i]-(mid-l+1)*h[mid] &lt;= f[l][mid-1]+((i+1)-mid+1)*h[mid]-f[mid+1][i+1]-(mid-l+1)*h[mid] f[l][mid1]+(imid+1)h[mid]f[mid+1][i](midl+1)h[mid]<=f[l][mid1]+((i+1)mid+1)h[mid]f[mid+1][i+1](midl+1)h[mid]
= &gt; =&gt; =>
f [ m i d + 1 ] [ i + 1 ] − f [ m i d + 1 ] [ i ] &lt; = h [ m i d ] f[mid+1][i+1]-f[mid+1][i] &lt;= h[mid] f[mid+1][i+1]f[mid+1][i]<=h[mid]
这个式子显然是满足的,即得证。


L O J LOJ LOJ代码, U O J UOJ UOJ简单魔改就好了。

#include <ctime>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long LL;
LL _min(LL x, LL y) {return x < y ? x : y;}
const int N = 750001;
int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * f;
}
void put(int x) {
	if(x >= 10) put(x / 10);
	putchar(x % 10 + '0');
}

LL ans[N];
int n, Q, h[N];
struct st_table {
	int f[20][N], bin[21], Log[N];
	
	int _max(int x, int y) {return h[x] >= h[y] ? x : y;}
	
	void bt() {
		bin[0] = 1; for(int i = 1; i <= 20; i++) bin[i] = bin[i - 1] * 2;
		Log[1] = 0; for(int i = 2; i <= n; i++) Log[i] = Log[i >> 1] + 1;
		for(int i = 1; i <= n; i++) f[0][i] = i;
		for(int i = 1; bin[i] <= n; i++) {
			for(int j = 1; j <= n - bin[i] + 1; j++) {
				f[i][j] = _max(f[i - 1][j], f[i - 1][j + bin[i - 1]]);
			}
		}
	}
	
	int query(int l, int r) {
		int hh = Log[r - l + 1];
		return _max(f[hh][l], f[hh][r - bin[hh] + 1]);
	}
} st;
struct query{int l, r;} g[N];
struct cc {LL a, b;};
vector<int> q[N];
struct tnode {
	int lc, rc; LL c;
	cc ad, cg;
};
struct segment {
	tnode t[N << 1];
	cc n1, n2;
	int cnt;
	
	void bt(int l, int r) {
		int now = ++cnt;
		t[now].lc = t[now].rc = -1;
		t[now].c = 0, t[now].ad = t[now].cg = cc{0, 0};
		if(l < r) {
			int mid = (l + r) / 2;
			t[now].lc = cnt + 1; bt(l, mid);
			t[now].rc = cnt + 1; bt(mid + 1, r);
		}
	}
	
	void build() {cnt = 0; bt(1, n);}
	
	void change(int now, int r, cc x) {
		t[now].cg = x; t[now].ad = cc{0, 0};
		t[now].c = x.a + x.b * r;
	}
	
	void add(int now, int r, cc x) {
		if(t[now].cg.a || t[now].cg.b) t[now].cg.a += x.a, t[now].cg.b += x.b;
		else t[now].ad.a += x.a, t[now].ad.b += x.b;
		t[now].c += x.a + x.b * r;
	}
	
	void pushdown(int now, int l, int r) {
		int mid = (l + r) / 2;
		if(t[now].cg.a || t[now].cg.b) {
			change(t[now].lc, mid, t[now].cg);
			change(t[now].rc, r, t[now].cg);
			t[now].cg = cc{0, 0};
		} if(t[now].ad.a || t[now].ad.b) {
			add(t[now].lc, mid, t[now].ad);
			add(t[now].rc, r, t[now].ad);
			t[now].ad = cc{0, 0};
		}
	}
	
	void Link(int now, int l, int r, int p, LL c) {
		if(l == r) {t[now].c = c; return ;}
		pushdown(now, l, r);
		int mid = (l + r) / 2;
		if(p <= mid) Link(t[now].lc, l, mid, p, c);
		else Link(t[now].rc, mid + 1, r, p, c);
		t[now].c = t[t[now].rc].c;
	}
	
	LL query(int now, int l, int r, int p) {
		if(l == r) return t[now].c;
		pushdown(now, l, r);
		int mid = (l + r) / 2;
		if(p <= mid) return query(t[now].lc, l, mid, p);
		else return query(t[now].rc, mid + 1, r, p);
	}
	
	void G1(int now, int l, int r, int ll, int rr) {
		if(ll == l && rr == r) {add(now, r, n1); return ;}
		pushdown(now, l, r);
		int mid = (l + r) / 2;
		if(rr <= mid) G1(t[now].lc, l, mid, ll, rr);
		else if(ll > mid) G1(t[now].rc, mid + 1, r, ll, rr);
		else G1(t[now].lc, l, mid, ll, mid), G1(t[now].rc, mid + 1, r, mid + 1, rr);
		t[now].c = t[t[now].rc].c;
	}
	
	void G2(int now, int l, int r, int ll, int rr) {
		if(ll == l && rr == r) {change(now, r, n2); return ;}
		pushdown(now, l, r);
		int mid = (l + r) / 2;
		if(rr <= mid) G2(t[now].lc, l, mid, ll, rr);
		else if(ll > mid) G2(t[now].rc, mid + 1, r, ll, rr);
		else G2(t[now].lc, l, mid, ll, mid), G2(t[now].rc, mid + 1, r, mid + 1, rr);
		t[now].c = t[t[now].rc].c;
	}
	
	void gai(int now, int l, int r, int ll, int rr) {
		if(l == r) {
			t[now].c = _min(n2.a + n2.b * l, t[now].c + n1.a);
			return ;
		} pushdown(now, l, r);
		int mid = (l + r) / 2;
		if(rr <= mid) gai(t[now].lc, l, mid, ll, rr);
		else if(ll > mid) gai(t[now].rc, mid + 1, r, ll, rr);
		else {
			if(t[t[now].lc].c + n1.a <= n2.a + n2.b * mid) {
				gai(t[now].lc, l, mid, ll, mid);
				G1(t[now].rc, mid + 1, r, mid + 1, rr);
			} else {
				gai(t[now].rc, mid + 1, r, mid + 1, rr);
				G2(t[now].lc, l, mid, ll, mid);
			}
		} t[now].c = t[t[now].rc].c;
	}
	
	void modify(int l, int r, LL a, LL b, LL c) {
		n1.a = b, n1.b = 0;
		n2.a = a - c * l + c, n2.b = c;
		gai(1, 1, n, l, r);
	}
} t;


LL dfs(int l, int r) {
	if(l > r) return 0;
	if(l == r) {t.Link(1, 1, n, l, h[l]); return h[l];}
	int mid = st.query(l, r);
	LL lans = dfs(l, mid - 1);
	LL rans = dfs(mid + 1, r);
	t.Link(1, 1, n, mid, lans + h[mid]);
	for(int i = 0; i < q[mid].size(); i++) {
		int x = q[mid][i];
		int ll = g[x].l, rr = g[x].r;
		if(mid < rr) ans[x] = _min(ans[x], t.query(1, 1, n, rr) + (LL)(mid - ll + 1) * h[mid]);
	} if(mid < r) t.modify(mid + 1, r, lans + h[mid], (LL)h[mid] * (mid - l + 1), h[mid]);
	return t.query(1, 1, n, r);
}

void solve() {
	st.bt();
	for(int i = 1; i <= n; i++) q[i].clear();
	for(int i = 1; i <= Q; i++) {
		if(g[i].l == g[i].r) ans[i] = h[g[i].l];
		else q[st.query(g[i].l, g[i].r)].push_back(i);
	} t.build();
	dfs(1, n);
}

int main() {
	memset(ans, 63, sizeof(ans));
	n = read(), Q = read();
	for(int i = 1; i <= n; i++) h[i] = read();
	for(int i = 1; i <= Q; i++) g[i].l = read() + 1, g[i].r = read() + 1;
	solve();
	reverse(h + 1, h + n + 1);
	for(int i = 1; i <= Q; i++) g[i].l = n - g[i].l + 1, g[i].r = n - g[i].r + 1, swap(g[i].l, g[i].r);
	solve();
	for(int i = 1; i <= Q; i++) printf("%lld\n", ans[i]);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值