年轻人的第一道线段树二分

Vases and Flowers - HDU 4614 - Virtual Judge

题意不再赘述.

解题:

对于操作1:从a点开始插花,直到插花完成或者插不完了

011010010

首先,这个过程绝不可能模拟去完成,只需要想一想就知道时间复杂度是O(N),比如上面的01序列,0表示可以插花,1表示不能插花。

思维了一圈,我们发现只要确定插花的一个区间就可以了

比如从a点开始插花那么我们插花的区间设为[L,R],(L>=a)

所以 要得出L,R 既第一个插花位置和最后一个插花位置

由此 引出我们的重点 二分

为什么可以二分?

首先我们先认为线段树维护的S表示区间内可以插花的花瓶的数量

原因是线段树是二叉树,天生就适合二分,并且线段树维护的区间和S具有单调性

所以 使用二分找到第一个插花位置和最后一个插花位置

思想:在a点开始 往后插花 第一个位置的S应该满足什么样的性质?

应该满足[a,L]插满了花 如果S表示(可以插花的数量) 那么S应该>=1,表示至少要有一个空位置用来插花 如果S表示(已经插了花的花瓶) 那么(L-a+1)-S>=1 只不过S表示(可以插花的数量) 更为方便

再思想 最后一个位置插花的S应该满足什么样的性质?

假如在[L,R]插花K朵,最后一个位置的S必须满足S>=K 既这个区间空位置数量大于等于K

其他细节见代码给出了详细的解释

#include<iostream>
#define lc p<<1
#define rc p<<1|1
using namespace std;
const int N = 50003;
struct Node{
	//我建议你不要在线段树里开l,r
	//因为占空间比较大
	int s, lazy;
}t[N<<2]; int n, m;
//s:没插花的数量
//lazy:题目设计到插花 拔花
//初始化lazy=-1表示什么也不做
//lazy=1表示拔花 把区间的空位S置为区间长度
//lazy=0表示插花 把区间的空位S置为0
void pushdown(int p,int l,int r) {
	if (t[p].lazy == -1) return;
	int mid = (l + r) >> 1;
	t[lc].s = (mid - l + 1) * t[p].lazy;
	t[rc].s = (r - mid) * t[p].lazy;
	t[lc].lazy = t[rc].lazy = t[p].lazy;
	t[p].lazy = -1;
}
void pushup(int p) {
	t[p].s = t[lc].s + t[rc].s;
}
void build(int p, int l, int r) {
	t[p] = { 1,-1 };
	if (l == r)return;
	int m = l + r >> 1;
	build(lc, l, m); build(rc, m + 1, r);
	pushup(p);
}
//L,R 表示区间修改区间
//l,r 表示节点区间
void update(int p, int L, int R, int l, int r, int k) {
	if (L > r || R < l) return;
	if (L <= l && r <= R) {
		//这里的处理比较通用 k=1表示拔花 把空位S置为区间长度
		//k=0表示插花 S=0表示区间空位置=0
		t[p].s = (r - l + 1)*k;
		t[p].lazy = k;
		return;
	}
	pushdown(p, l, r);
	int m = l + r >> 1;
	update(lc, L, R, l, m, k);
	update(rc, L, R, m + 1, r, k);
	pushup(p);
}
//L,R 表示区间查询区间
//l,r 表示节点区间
int query(int p, int L, int R, int l, int r) {
	if (L > r || R < l) return 0;
	if (L <= l && r <= R) {
		return t[p].s;
	}
	pushdown(p, l, r);
	int m = l + r >> 1;
	int sum = 0;
	sum+=query(lc, L, R, l, m);
	sum+=query(rc, L, R, m + 1, r);
	return sum;
}
int binarysearch(int L, int R, int target) {
	//在[L,R]二分得出第一朵花位置和最后一朵花位置
	int l = L, r = R; int res = 0;
	while (l <= r) {
		int mid = (l + r) >> 1;
		//---L------M------R-----
		int t = query(1, L, mid, 1, n);//查询[L,M]的空位置
		if (t >= target) {//空位置数量足够
			res = mid;
			r = mid-1;
		}
		else {
			l = mid + 1;
		}
	}
	return res;
}
void solve() {
	 cin >> n >> m;
	build(1, 1, n);
	for (int i = 1; i <= m; i++) {
		int op; cin >> op;
		if (op == 1) {
			//a:起始位置
			//f:插花数量
			int a, f; cin >> a >> f;
			//a++是因为题目下标是[0,N-1] 处理不方便
			a++;
			//首先查询[a,n]的空位置数量
			int cnt = query(1, a, n, 1, n);
			if (cnt == 0) {
				cout << "Can not put any one." << endl;
			}
			else {
				//题意:从a位置开始插花 插不完就扔掉
				//只有cnt个空位置 f朵花
				//肯定取最小值插花
				f = min(f, cnt);
				//s:[a,n]范围内第一朵花
				//e:[a,n]范围内最后一朵花
				int s = binarysearch(a, n, 1);
				int e = binarysearch(a, n, f);
				cout << s - 1 << ' ' << e - 1 << endl;
				//把区间空位置修改为0
				update(1, s, e, 1, n, 0);
			}
		}
		else {
			int a, b; cin >> a >> b;
			a++; b++;
			//query返回的是空位置数量
			//区间长度减去空位置数量就是花的数量
			cout << (b-a+1) - query(1, a, b, 1, n) << endl;
			//把区间空位置为长度
			update(1, a, b, 1, n, 1);
		}
	}
	cout << endl;
}
int main() {
	int t; cin >> t;
	while (t--)
		solve();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值