UVA1400 “Ray, Pass me the dishes!“ 的题解

题目大意

很简洁,传送门

如果你想锻炼英语,请看这里:传送门

思路

整体来看,我的代码应该是比较短、比较快的了。

这题需要维护 动态 的最大子段和。首先你可能会想到 RMQ,但是由于是动态的,而不是像 P1115 这题静态的,所以立刻放弃这种算法。

那么我们就需要功能强大的利器——线段树了。

普通的线段树大家想必都会,不会的萌新先做做 这题 吧。有兴趣,或者还想提高还可以做 这题 哦。

知道了这个,单点修改应该就没问题了,主要问题就在于 怎样用线段树维护一个区间的最大子段和呢


为了解决这个问题,我们必须设一个结构体来存储线段树,具体代码如下:

struct node {
	ll pre, suf; //前缀和、后缀和 
	ll maxsub, val;  //最大子段和、区间和 
	int l, r; //子段和左端点、右端点 
	int pre_r, suf_l; //前缀和 右端点,后缀和 左端点 
} tree[500007 * 4];

然后再贴一个单点修改的代码吧,代码只要根据结构体的变化而变化,我不做解释了:

node query(int root, int l, int r, int x, int y) {
	if(l >= x && r <= y) { return tree[root]; }
	int mid = (l + r) / 2;
	bool flag1 = 0, flag2 = 0;
	node s, b, ans;
	if(x <= mid) s = query(root << 1, l, mid, x, y), flag1 = 1;
	if(y > mid) b = query(root << 1 | 1, mid + 1, r, x, y), flag2 = 1;
	if(flag1 && flag2) ans = renew(s, b, root);
	else if(flag1) ans = s;
	else if(flag2) ans = b;
	return ans;
}

同时,我们还需要一个用来更新线段树的最大子段和(其实就是参数)的函数。到了这里,就需要一点点思维难度了。
首先,普通线段树模板里的 tree[root] = tree[root << 1] + tree[root << 1 | 1];肯定是不行的。于是我们可以对于线段树每一个参数(见上)进行处理。具体实现我觉得也不太好说,仔细看我下面的代码应该就能领会到意思了:

node renew(node x, node y, int root) { //更新 tree[root] 
	node a = (node){0, 0, 0, x.val + y.val, 0, 0, 0, 0};
	//理解这一段一定要画图,画图,画图!!! 
	//前缀,取最大值加入 a,同时更新前缀和的右端点 
	if(x.val + y.pre <= x.pre) { a.pre = x.pre; a.pre_r = x.pre_r; }
	else { a.pre = x.val + y.pre; a.pre_r = y.pre_r; }
	//后缀,取最大值加入 a,同时更新后缀和的左端点 
	if(x.suf + y.val < y.suf) { a.suf = y.suf; a.suf_l = y.suf_l; }
	else { a.suf = x.suf + y.val; a.suf_l = x.suf_l; }
	//字段和,取最大值加入 a,同时更新左右儿子 
	if(x.suf + y.pre <= x.maxsub && x.maxsub >= y.maxsub) //左子树 
		a.maxsub = x.maxsub, a.l = x.l, a.r = x.r;
	else if(x.suf + y.pre >= x.maxsub && x.suf + y.pre >= y.maxsub) //跨越左右子树 
		a.maxsub = x.suf + y.pre, a.l = x.suf_l, a.r = y.pre_r;
	else a.maxsub = y.maxsub, a.l = y.l, a.r = y.r; //右子树 
	return a;
}

ok,分析就到这了,如果有简洁的非线段树做法(作者很期待哦)欢迎私信我哦。整体代码如下:

//对拍的程序 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m, cnt = 0, w[500007];
struct node {
	ll pre, suf; //前缀和、后缀和 
	ll maxsub, val;  //最大子段和、区间和 
	int l, r; //子段和左端点、右端点 
	int pre_r, suf_l; //前缀和 右端点,后缀和 左端点 
} tree[500007 * 4]; //呼 ~ 好累啊 
inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
	return x * f;
}
node renew(node x, node y, int root) { //更新 tree[root] 
	node a = (node){0, 0, 0, x.val + y.val, 0, 0, 0, 0};
	//理解这一段一定要画图,画图,画图!!! 
	//前缀,取最大值加入 a,同时更新前缀和的右端点 
	if(x.val + y.pre <= x.pre) { a.pre = x.pre; a.pre_r = x.pre_r; }
	else { a.pre = x.val + y.pre; a.pre_r = y.pre_r; }
	//后缀,取最大值加入 a,同时更新后缀和的左端点 
	if(x.suf + y.val < y.suf) { a.suf = y.suf; a.suf_l = y.suf_l; }
	else { a.suf = x.suf + y.val; a.suf_l = x.suf_l; }
	//字段和,取最大值加入 a,同时更新左右儿子 
	if(x.suf + y.pre <= x.maxsub && x.maxsub >= y.maxsub) //左子树 
		a.maxsub = x.maxsub, a.l = x.l, a.r = x.r;
	else if(x.suf + y.pre >= x.maxsub && x.suf + y.pre >= y.maxsub) //跨越左右子树 
		a.maxsub = x.suf + y.pre, a.l = x.suf_l, a.r = y.pre_r;
	else a.maxsub = y.maxsub, a.l = y.l, a.r = y.r; //右子树 
	return a;
}
inline void build_tree(int root, int l, int r) {
	if(l == r) { //稍微有点改动,但是显而易见 
		tree[root] = (node){w[l], w[l], w[l], w[l], l, l, l, l};
		return ;
	}
	int mid = (l + r) / 2;
	build_tree(root << 1, l, mid);
	build_tree(root << 1 | 1, mid + 1, r);
	tree[root] = renew(tree[root << 1], tree[root << 1 | 1], root);
}
node query(int root, int l, int r, int x, int y) {
	if(l >= x && r <= y) { return tree[root]; }
	int mid = (l + r) / 2;
	bool flag1 = 0, flag2 = 0;
	node s, b, ans;
	if(x <= mid) s = query(root << 1, l, mid, x, y), flag1 = 1;
	if(y > mid) b = query(root << 1 | 1, mid + 1, r, x, y), flag2 = 1;
	if(flag1 && flag2) ans = renew(s, b, root);
	else if(flag1) ans = s;
	else if(flag2) ans = b;
	return ans;
}
int main() {
//	freopen("live.in", "r", stdin);
//	freopen("live.out", "w", stdout);
	while(~scanf("%d %d", &n, &m)) { //读入技巧 
		memset(w, 0, sizeof(w));
		memset(tree, 0, sizeof(tree)); //后面的 n * 4 + 1 可以有效缩短时间,上次网校模拟赛就是因为这个掉到 85 的 ┭┮﹏┭┮ 
		cnt++;
		printf("Case %d:\n", cnt);
		for(int i = 1; i <= n; i++) w[i] = read();=
		build_tree(1, 1, n);
		for(int i = 1; i <= m; i++) {
			int a, b;
			scanf("%d %d", &a, &b);
			node tmp = query(1, 1, n, a, b);
			printf("%d %d\n", tmp.l, tmp.r);
		}
	}
	return 0;
}
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值