【BZOJ 1926】[Sdoi2010]粟粟的书架

题目来源:BZOJ 1926

思路:

考虑分成两个部分做,R=1是可以考虑用主席树维护,R,C是200的时候考虑用三维前缀和暴力。
具体的,主席树的做法与区间求第K个数类似,当时维护的权值出现的次数,现在维护的是二元组(数字出现的次数,所有数字之和);暴力的做法是预处理 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示前i行,前j列,权值小于k的元素和, g [ i ] [ j ] [ k ] g[i][j][k] g[i][j][k]表示前i行,前j列,权值小于k的元素个数,对每一个询问暴力枚举k的值,找到第一个大于 H i H_i Hi的输出 g [ . . . ] [ . . . ] [ . . . ] g[...][...][...] g[...][...][...]就好了。
当然,二分的时候边界注意适当的调整,有可能很多个相同的只选择一个。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define mid ((l+r)>>1)
using namespace std;
const int maxn = 5e5+5;
const int n = 1000;
int r, c, m;
int f[201][201][1001], g[201][201][1001], val[201][201];
int cnt, a1[maxn];
struct Tree{
	int num, sum;
	Tree* ch[2];
}T[maxn*13], *root[maxn];
Tree* get(){return &T[++ cnt];}
void made(Tree *now, int l, int r){
	if(l == r) return;
	made(now->ch[0] = get(), l, mid);
	made(now->ch[1] = get(), mid+1, r);
}
void build(Tree* now, Tree* pre, int val, int l, int r){
	if(l == r) return;
	int wh = 1;
	if(val <= mid) wh = 0;
	now->ch[wh^1] = pre->ch[wh^1];
	now->ch[wh] = get();
	now->ch[wh]->num = pre->ch[wh]->num + 1;
	now->ch[wh]->sum = pre->ch[wh]->sum + val;
	build(now->ch[wh], pre->ch[wh], val, 
	wh == 0 ? l : mid+1, wh == 0 ? mid : r);
}
int ask(Tree* fi, Tree* se, int val, int l, int r, int &sum, int &num){
	if(l == r){
		sum += se->sum - fi->sum;
		num += se->num - fi->num;
		return l;
	} 
	int lch = se->ch[0]->sum - fi->ch[0]->sum;
	int rch = se->ch[1]->sum - fi->ch[1]->sum;
	if(val <= rch) return ask(fi->ch[1], se->ch[1], val, mid+1, r, sum, num);
	else{
		sum += rch, num += se->ch[1]->num - fi->ch[1]->num;
		return ask(fi->ch[0], se->ch[0], val-rch, l, mid, sum, num);
	}
}

int main(){
	scanf("%d%d%d", &r, &c, &m);
	if(r != 1){
		for(int i = 1; i <= r; i ++)
			for(int j = 1; j <= c; j ++){
				scanf("%d", &val[i][j]);
				for(int k = 0; k <= n; k ++){
					f[i][j][k] = f[i-1][j][k] + f[i][j-1][k] - f[i-1][j-1][k] + (val[i][j] >= k ? val[i][j] : 0);
					g[i][j][k] = g[i-1][j][k] + g[i][j-1][k] - g[i-1][j-1][k] + (val[i][j] >= k ? 1 : 0);
				}
			}
		for(int i = 1; i <= m; i ++){
			int x1, y1, x2, y2, kk, ok = 1;
			scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &kk);
			for(int j = n; j >= 0; j --){
				int t = f[x2][y2][j] + f[x1-1][y1-1][j] - f[x2][y1-1][j] - f[x1-1][y2][j];
				if(t >= kk){
					printf("%d\n", g[x2][y2][j] + g[x1-1][y1-1][j] - g[x2][y1-1][j] - g[x1-1][y2][j] - (t-kk)/j);
					ok = 0; break;
				}
			}
			if(ok) printf("Poor QLW\n");
		}
		return 0;
	}
	for(int i = 1; i <= c; i ++) scanf("%d", &a1[i]);
	root[0] = get(), made(root[0], 1, n);
	for(int i = 1; i <= c; i ++){
		root[i] = get();
		build(root[i], root[i-1], a1[i], 1, n);
	}
	for(int i = 1; i <= m; i ++){
		int a1, b1, c1, d1, val;
		scanf("%d%d%d%d%d", &a1, &b1, &c1, &d1, &val);
		int sum = 0, num = 0;
		int t = ask(root[b1-1], root[d1], val, 1, n, sum, num);
		if(sum < val){printf("Poor QLW\n"); continue;}
		printf("%d\n", num - (sum-val)/t);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值