【wikioi】1217 借教室

题目链接http://www.wikioi.com/problem/1217/

算法:二分答案(线段树可过wikioi数据)

  • 二分http://www.wikioi.com/solution/list/1217/   (我不多阐述)

    不难看出这道题满足二分条件 所以我们对数据进行二分

    维护一个具有前缀和性质的数组sum记录当前二分区间内的教室需求情况,那么第i天需要的教室数为∑sum(1→i)。

    维护方法:若订单i是从si天到ti天要借di个教室,那么sum[si]+=di,sum[ti+1]-=di。

    计算每天的需求量是否超出教室量,如果有,答案必定在该区间,继续二分该区间,否则答案应该在另一个区间,继续二分。

    (如果答案是0,一直二分下去肯定会有l = r的情况,然后就跳出来了=-=,此时m = l - 1)

     丧心病狂の加速:如果不是二分区间内的而且没读入的数据,你读它什么呢?233

     (这样写的好处有:①比线段树代码短②比线段树速度快 >_<)

    我的代码

    #include <cstdio>
    using namespace std;
    
    #define MID (l+r) >> 1
    
    const int maxn = 1000000+10;
    int sum[maxn], cl[maxn], d[maxn], s[maxn], t[maxn], n, m, last, last_q;
    
    int check(int r) {
    	int ret = 0;
    	for(int i = 1; i <= n; ++i) { //这里必须要到n,因为是用前缀和, 即sum[t[i]]的t[i]后可能还有sum= =。。
    		ret += sum[i];
    		if(ret > cl[i]) return 0;
    	}
    	return 1;
    }
    
    int main() {
    	scanf("%d%d", &n, &m);
    	int l = 1, r = m, mid, i;
    	for(i = 1; i <= n; ++i) scanf("%d", &cl[i]);
    	while(l <= r) {
    		mid = MID;
    		if(last_q < mid){
    			for(i = last+1; i <= mid; ++i) scanf("%d%d%d", &d[i], &s[i], &t[i]);
    			last_q = mid;
    		}
    		//这里最容易错,要清楚标记
    		for(i = mid+1; i <= last; ++i) sum[s[i]] -= d[i], sum[t[i]+1] += d[i]; //将原来加上去的和减的恢复
    		for(i = last+1; i <= mid; ++i) sum[s[i]] += d[i], sum[t[i]+1] -= d[i]; //加上去的和减去新添加的元素
    		last = mid;
    		if(check(mid)) l = mid+1;
    		else r = mid-1;
    	}
    	//一般闭区间二分的答案是l-1,在这里,l-1=m时说明所有人都满足
    	if(l-1 == m) printf("0");
    	else printf("-1\n%d", l); //而因为l-1是答案,所以l-1+1=l就是下一个不符合答案的 
    	return 0;
    }
    
  •  线段树(区间更新和维护最小)

    注意:用线段树最容易错的地方就是认为当修改后全部教室加起来的和为负的那个人就是解。其实应该是每天的教室需求量,不是和。所以不能维护区间和,要维护一个最小值,最小值为负,说明这个人就是答案

    #include <cstdio>
    using namespace std;
    
    #define lson l, m, rt << 1
    #define rson m+1, r, rt << 1 | 1
    #define MID (l+r)>>1
    #define lc rt << 1
    #define rc rt << 1 | 1
    
    int min(const int& a, const int& b){return a < b ? a : b;}
    
    const int maxn = 1e6+10;
    int minx[maxn << 2], add[maxn << 2], n, m, L, R, _add;
    
    //向上传递最小的
    void pushup(int rt) {
    	minx[rt] = min(minx[lc], minx[rc]);
    }
    
    //向下传递修改值
    void pushdown(int rt) {
    	if(add[rt]) {
    		add[lc] += add[rt];
    		add[rc] += add[rt];
    		minx[lc] += add[rt];
    		minx[rc] += add[rt];
    		add[rt] = 0;
    	}
    }
    
    void build(int l, int r, int rt) {
    	add[rt] = 0;
    	if(l == r) {
    		scanf("%d", &minx[rt]);
    		return;
    	}
    	int m = MID;
    	build(lson); build(rson);
    	pushup(rt);
    }
    
    void update(int l, int r, int rt) {
    	if(L <= l && r <= R) {
    		add[rt] += _add;
    		minx[rt] += _add; //这里直接修改即可
    		return;
    	}
    	pushdown(rt);
    	int m = MID;
    	if(L <= m) update(lson);
    	if(m < R) update(rson);
    	pushup(rt);
    }
    
    int main() {
    	scanf("%d%d", &n, &m);
    	build(1, n, 1);
    	int i;
    	for(i = 1; i <= m; ++i) {
    		scanf("%d%d%d", &_add, &L, &R);
    		_add = -_add;
    		update(1, n, 1);
    		if(minx[1] < 0) { printf("-1\n%d", i); return 0;}
    	}
    	printf("0");
    	return 0;
    }
    

转载于:https://www.cnblogs.com/iwtwiioi/p/3536121.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值