线段树好题

15 篇文章 0 订阅
3 篇文章 1 订阅

最近等对

(线段树+离线+双指针)

题意:
有一个序列 a 1 a1 a1 , a 2 a2 a2 , a 3 a3 a3 ,…, a n an an , 还有 m m m 次查询 l , r l , r l,r,对于每一个查询,找出距离最近的 x x x y y y,且满足 a [ x ] = = a [ y ] a[x]==a[y] a[x]==a[y] , 两个数字的距离是他们下标之差的绝对值。

这里的序列的长度是5e5的,里面的查询数量也是到5e5量级的,那显然复杂度最高就到 n l o g n n logn nlogn的量级,而每次查询区间的时候最多只能有一个log的操作,那么如果不对给定的区间进行预处理的话,确实没有办法来实现刚好完全在这个区间的最小值。想到离线操作,离线完了之后就要一个挑让我们最舒服的对查询区间的 l , r l , r l,r 进行排序。然后这里就要把最小值存起来,因为每个值必定是和自己左边和右边的相同的值是最近的,那就统一在相同的数的左边里记他们之间的距离,这样把查询区间按右端点从小到大进行排序。同时开双指针进行查找就可以解决了,里面的查询区间最小值就可以用线段树来完成

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
int n, m;
const int maxn = 5e5 + 100;
int a[maxn], b[maxn];
int last[maxn], lef[maxn];
int ans[maxn];
struct node {
	int id, l, r;
	bool  operator<(const node& rhs) const {
		return r < rhs.r;
	}
}q[maxn];

int read() {
	int x = 0, f = 1;
	char c = getchar();
	while (c<'0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
	while (c >= '0'&&c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}

void write(int x) {
	if (x < 0) putchar('-'), x = -x;
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

const int INF = 0x3f3f3f3f;
int tree[maxn << 2];
void build(int p, int l, int r) {
	if (l == r) tree[p] = INF;
	else {
		int mid = (l + r) >> 1;
		build(p << 1, l, mid);
		build(p << 1 | 1, mid + 1, r);
		tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
	}
}
int query_min(int p, int l, int r, int x, int y) {
	if (l >= x && r <= y) return tree[p];
	int mid = (l + r) >> 1;
	int minn = INF;
	if (x <= mid) minn = min(minn, query_min(p * 2, l, mid, x, y));
	if (y > mid) minn = min(minn, query_min(p * 2 | 1, mid + 1, r, x, y));
	return minn;
}

void update(int p, int l, int r, int x, int k) {
	if (l == r) {
		tree[p] = k;
		return;
	}
	int mid = (l + r) >> 1;
	if (x <= mid) update(p << 1, l, mid, x, k);
	else update(p << 1 | 1, mid + 1, r, x, k);
	tree[p] = min(tree[p << 1] , tree[p << 1 | 1]);
}

void solve() {
	sort(b + 1, b + 1 + n);
	int cnt1 = unique(b + 1, b + 1 + n) - b - 1;
	for (int i = 1; i <= n; i++) {
		a[i] = lower_bound(b + 1, b + 1 + cnt1, a[i]) - b;
		lef[i] = last[a[i]];
		last[a[i]] = i;
	}
	int pos = 1; int cnt = 1;
	build(1, 1, n);
	while (pos <= n && cnt <= q[m].r) {
		if (lef[cnt]) {
			//cout << " " << lef[cnt] << " " << cnt - lef[cnt] << endl;
			update(1, 1, n, lef[cnt], cnt - lef[cnt]);
		}
		while (q[pos].r == cnt) {
			//cout << " " << "** " <<cnt<<" "<<pos<<" "<< q[pos].l << " " << q[pos].r << endl;
			int ans1 = query_min(1, 1, n, q[pos].l, q[pos].r);
			if (ans1 == INF) ans[q[pos].id] = -1;
			else ans[q[pos].id] = ans1;
			pos++;
		}
		cnt++;
	}
	for (int i = 1; i <= m; i++) {
		write(ans[i]);
		printf("\n");
	}
}

signed main() {
	ios::sync_with_stdio; cin.tie(0); cout.tie(0);
	n = read(); m = read();
	for (int i = 1; i <= n; i++) a[i]=read(), b[i] = a[i];
	for (int i = 1; i <= m; i++) q[i].l=read(),q[i].r=read(), q[i].id = i;
	sort(q + 1, q + 1 + m);
	solve();
	return 0;
}

公园晨跑

线段树+思维

题意:有 n n n颗树,第 i i i棵树和第 i + 1 i+1 i+1之间的距离是 d [ i ] d [ i ] d[i] ,而第 n n n 棵树和第一棵树的距离是 d [ n ] d [ n ] d[n] 。第 i i i棵的高度是 d [ i ] d [ i ] d[i]
猴子每天要选择两棵树,然后再第一棵树下来,接着围绕公园跑,但是会有小孩在 a [ i ] a [ i ] a[i] b [ i ] b [ i ] b[i], 在这里的范围里面猴子是不能经过的,需要帮猴子找到两棵树,使他晨跑时能够消耗最大的能量。
首先把环先拆成2*n的线性图,已知要求的是 2 h x + 2 h y + s u m y − s u m x ( x < y ) 2hx+2hy+sumy-sumx (x<y) 2hx+2hy+sumysumx(x<y) s u m x sumx sumx代表x到0前缀和,相减的话就是y到x的距离,而要求这段距离最大,这里的未知量有两个,有x和y,所以不能这样去求最大值,把这段距离分成求 2 h y + s u m y 2hy+sumy 2hy+sumy的最大值和求 s u m x − 2 h y sumx-2hy sumx2hy的最小值。给定范围,然后用线段树区间查询最大值和最小值,这里还要考虑一个问题,那就是x==y的情况,这样的话,把树的节点值打真正的值的下标,这样的话就能快速比较出是否是相同的值,然后缩小区间进行求值,求出答案。

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define int long long
int read() {
	int x = 0, f = 1;
	char c = getchar();
	while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
const int maxn = 2e5 + 10;
int n, m;
int d[maxn], h[maxn];
int a[maxn], b[maxn];
void init() {
	int sum = 0;
	a[0] = -1e9; b[0] = -1e9;
	for (int i = 1; i <= n; i++) {
		sum += d[i - 1];
		a[i] = sum + 2 * h[i];
		b[i] = sum - 2 * h[i];
		//cout << i << " " << a[i] << " " << b[i] << endl;
	}
}


const int INF = 0x3f3f3f3f;
int treemin[maxn << 2];
int treemax[maxn << 2];

int Max(int a1, int b1) { return a[a1] > a[b1] ? a1 : b1; }
int Min(int a1, int b1) { return b[a1] < b[b1] ? a1 : b1; }

void build(int p, int l, int r) {
	if (l == r) treemin[p] = l, treemax[p] = l;
	else {
		int mid = (l + r) >> 1;
		build(p << 1, l, mid);
		build(p << 1 | 1, mid + 1, r);
		treemin[p] = Min(treemin[p << 1], treemin[p << 1 | 1]);
		treemax[p] = Max(treemax[p << 1], treemax[p << 1 | 1]);
	}
}

int query_min(int p, int l, int r, int x, int y) {
	if (l >= x && r <= y) return treemin[p];
	int mid = (l + r) >> 1;
	int minn = x;
	if (x <= mid) minn = Min(minn, query_min(p * 2, l, mid, x, y));
	if (y > mid) minn = Min(minn, query_min(p * 2 | 1, mid + 1, r, x, y));
	return minn;
}

int query_max(int p, int l, int r, int x, int y) {
	if (l >= x && r <= y) return treemax[p];
	int mid = (l + r) >> 1;
	int maxx = x;
	//cout << "maxx = " << maxx << endl;
	if (x <= mid) maxx = Max(maxx, query_max(p * 2, l, mid, x, y));
	if (y > mid) maxx = Max(maxx, query_max(p * 2 | 1, mid + 1, r, x, y));
	return maxx;
}

int solve(int l, int r) {
	//cout << "***" << l << " " << r << endl;
	int x = query_max(1, 1, n, l, r);
	int y = query_min(1, 1, n, l, r);
//	cout << x << " " << y << endl;
	if (x != y) return a[x] - b[y];
	int another_x, another_y;
	if (l > x - 1 && r < x + 1) another_x = x;
	else if (l > x - 1) another_x = query_max(1, 1, n, x + 1, r);
	else if (r < x + 1) another_x = query_max(1, 1, n, l, x - 1);
	else another_x = Max(query_max(1, 1, n, l, x - 1), query_max(1, 1, n, x + 1, r));

	if (l > y - 1 && r < y + 1) another_y = y;
	else if (l > y - 1) another_y = query_min(1, 1, n, y + 1, r);
	else if (r < y + 1) another_y = query_min(1, 1, n, l, y - 1);
	else another_y = Min(query_min(1, 1, n, l, y - 1), query_min(1, 1, n, y + 1, r));
	//cout <<"*****" << another_x << " " << another_y << endl;
	return max(a[another_x] - b[y], a[x] - b[another_y]);
}

signed main() {
	n = read(); m = read();
	n = n * 2;
	for (int i = 1; i <= n / 2; i++) d[i] = read(), d[i + n / 2] = d[i];
	for (int i = 1; i <= n / 2; i++) h[i] = read(), h[i + n / 2] = h[i];
	init();
	build(1, 1, n);
	while (m--) {
		int aa = read(), bb = read();
		if (aa <= bb) cout << solve(bb + 1, n / 2 + aa - 1) << endl;
		else cout << solve(bb + 1, aa - 1) << endl;
	}
	return 0;
}

Pinball

线段树+dp

题意:Pinball的游戏界面由m+2行、n列组成。第一行在顶端。一个球会从第一行的某一列出发,开始垂直下落,界面上有一些漏斗,一共有m个漏斗分别放在第2m+1行,第i个漏斗的作用是把经过第i+1行且列数在Ai~Bi之间的球,将其移到下一行的第Ci列。 使用第i个漏斗需要支付Di的价钱,你需要保留一些漏斗使得球无论从第一行的哪一列开始放,都只可能到达第m+2行的唯一 一列,求花费的最少代价。
样例的图
n的范围到达1e9,那就要考虑离散化,保留需要的点,m的范围到1e5,且要求花费最小,考虑dp去写,以 d p 1 [ i ] dp1[ i ] dp1[i]来表示以 i i i块为从1到 d p 1 [ i ] dp1[ i ] dp1[i]的最小花费,同理以 d p 2 [ i ] dp2[ i ] dp2[i]来表示以 i i i块为从n到 d p 2 [ i ] dp2[ i ] dp2[i]的最小花费,但是维护 d p 1 [ i ] dp1[ i ] dp1[i]就需要从第 i i i块的 l 到 r l 到 r lr开始寻找最小值,这样的话,直接用线段树进行维护,不断单点更新第 i i i m m m 点,然后区间查询最小值,进行dp;最后答案为 d p 1 [ i ] + d p 2 [ i ] − a [ i ] . v a l dp1[ i ]+dp2[ i ]-a[ i ].val dp1[i]+dp2[i]a[i].val取最小的即可

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 3e5 + 100;
struct node {
	int l, r, m, val;
}a[maxn];
int read() {
	int x = 0, f = 1;
	char c = getchar();
	while (c < '0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
int b[maxn];

const int INF = 1e18;
int tree[maxn << 2];
void build(int p, int l, int r) {
	if (l == r) tree[p] = INF;
	else {
		int mid = (l + r) >> 1;
		build(p << 1, l, mid);
		build(p << 1 | 1, mid + 1, r);
		tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
	}
}

void update(int p, int l, int r, int x, int k) {
	if (l == r) {
		tree[p] = min(k, tree[p]);
		return;
	}
	int mid = (l + r) >> 1;
	if (x <= mid) update(p << 1, l, mid, x, k);
	else update(p << 1 | 1, mid + 1, r, x, k);
	tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
}




int query_min(int p, int l, int r, int x, int y) {
	if (l >= x && r <= y) return tree[p];
	int mid = (l + r) >> 1;
	int minn = INF;
	if (x <= mid) minn = min(minn, query_min(p * 2, l, mid, x, y));
	if (y > mid) minn = min(minn, query_min(p * 2 | 1, mid + 1, r, x, y));
	return minn;
}

int dpl[maxn], dpr[maxn];

signed main() {

	int m, n;
	m = read(); n = read();
	int cnt = 0;
	for (int i = 1; i <= m; i++) {
		a[i].l = read(); a[i].r = read(); a[i].m = read(); a[i].val = read();
		b[++cnt] = a[i].l; b[++cnt] = a[i].r; b[++cnt] = a[i].m;
	}
	b[++cnt] = 1;
	b[++cnt] = n;
	sort(b + 1, b + 1 + cnt);

	int cnt1 = unique(b + 1, b + 1 + cnt) - b - 1;
	for (int i = 1; i <= m; i++) {
		a[i].l = lower_bound(b + 1, b + 1 + cnt1, a[i].l) - b;
		a[i].r = lower_bound(b + 1, b + 1 + cnt1, a[i].r) - b;
		a[i].m = lower_bound(b + 1, b + 1 + cnt1, a[i].m) - b;
	}
	//从第1列丢出的和从第m列丢出的最后在同一个漏斗里面即可
	//
	//cout << cnt1 << endl;
	build(1, 1, cnt1);
	update(1, 1, cnt1, 1, 0);
	for (int i = 1; i <= m; i++) {
		int minn = query_min(1, 1, cnt1, a[i].l, a[i].r);
		if (minn < INF) update(1, 1, cnt1, a[i].m, minn + a[i].val);
		dpl[i] = minn + a[i].val;
	}
	build(1, 1, cnt1);
	update(1, 1, cnt1, cnt1, 0);
	//update(1, 1, n, m, 0);
	for (int i = 1; i <= m; i++) {
		int minn = query_min(1, 1, cnt1, a[i].l, a[i].r);
		if (minn < INF) update(1, 1, cnt1, a[i].m, minn + a[i].val);
		dpr[i] = minn + a[i].val;
	}
	int ans = INF;
	for (int i = 1; i <= m; i++) {
	//	cout << dpl[i] << " " << dpr[i] << endl;
		ans = min(dpl[i] + dpr[i] - a[i].val, ans);
	}
	if (ans < INF/2) printf("%lld\n", ans);
	else printf("-1\n");
	return 0;
}
/*
10 1000
230 675 581 645789359
92 380 153 694212514
496 696 685 668220566
676 1000 907 347463815
477 667 491 576572818
8 910 698 896379238
355 618 380 374691592
450 800 700 588527055
408 557 488 805749148
2 715 128 830530803


430

*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值